From a0b41cccef52d3ecb96dff65bb771989e2a5a293 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 7 Nov 2024 16:59:36 +0100 Subject: [PATCH 1/7] extract Knapsack integration from RSpec --- lib/datadog/ci/contrib/knapsack/extension.rb | 27 ++++++++ .../ci/contrib/knapsack/integration.rb | 51 +++++++++++++++ lib/datadog/ci/contrib/knapsack/patcher.rb | 33 ++++++++++ lib/datadog/ci/contrib/knapsack/runner.rb | 61 ++++++++++++++++++ .../contrib/rspec/knapsack_pro/extension.rb | 29 --------- .../ci/contrib/rspec/knapsack_pro/patcher.rb | 26 -------- .../ci/contrib/rspec/knapsack_pro/runner.rb | 62 ------------------- lib/datadog/ci/contrib/rspec/patcher.rb | 15 ----- sig/datadog/ci/contrib/knapsack/extension.rbs | 17 +++++ .../ci/contrib/knapsack/integration.rbs | 30 +++++++++ sig/datadog/ci/contrib/knapsack/patcher.rbs | 11 ++++ sig/datadog/ci/contrib/knapsack/runner.rbs | 23 +++++++ .../contrib/rspec/knapsack_pro/extension.rbs | 19 ------ .../ci/contrib/rspec/knapsack_pro/patcher.rbs | 13 ---- .../ci/contrib/rspec/knapsack_pro/runner.rbs | 25 -------- 15 files changed, 253 insertions(+), 189 deletions(-) create mode 100644 lib/datadog/ci/contrib/knapsack/extension.rb create mode 100644 lib/datadog/ci/contrib/knapsack/integration.rb create mode 100644 lib/datadog/ci/contrib/knapsack/patcher.rb create mode 100644 lib/datadog/ci/contrib/knapsack/runner.rb delete mode 100644 lib/datadog/ci/contrib/rspec/knapsack_pro/extension.rb delete mode 100644 lib/datadog/ci/contrib/rspec/knapsack_pro/patcher.rb delete mode 100644 lib/datadog/ci/contrib/rspec/knapsack_pro/runner.rb create mode 100644 sig/datadog/ci/contrib/knapsack/extension.rbs create mode 100644 sig/datadog/ci/contrib/knapsack/integration.rbs create mode 100644 sig/datadog/ci/contrib/knapsack/patcher.rbs create mode 100644 sig/datadog/ci/contrib/knapsack/runner.rbs delete mode 100644 sig/datadog/ci/contrib/rspec/knapsack_pro/extension.rbs delete mode 100644 sig/datadog/ci/contrib/rspec/knapsack_pro/patcher.rbs delete mode 100644 sig/datadog/ci/contrib/rspec/knapsack_pro/runner.rbs diff --git a/lib/datadog/ci/contrib/knapsack/extension.rb b/lib/datadog/ci/contrib/knapsack/extension.rb new file mode 100644 index 00000000..c3fb3b47 --- /dev/null +++ b/lib/datadog/ci/contrib/knapsack/extension.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "knapsack_pro/extensions/rspec_extension" + +require_relative "runner" + +module Datadog + module CI + module Contrib + module Knapsack + module Extension + def self.included(base) + base.singleton_class.prepend(ClassMethods) + end + + module ClassMethods + def setup! + super + + ::RSpec::Core::Runner.include(Datadog::CI::Contrib::Knapsack::Runner) + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/contrib/knapsack/integration.rb b/lib/datadog/ci/contrib/knapsack/integration.rb new file mode 100644 index 00000000..e41cc5af --- /dev/null +++ b/lib/datadog/ci/contrib/knapsack/integration.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require_relative "../integration" +require_relative "patcher" + +module Datadog + module CI + module Contrib + module Knapsack + # Knapsack Pro test runner instrumentation + # https://github.com/KnapsackPro/knapsack_pro-ruby + class Integration + include Datadog::CI::Contrib::Integration + + Configuration = Struct.new(:enabled) + + MINIMUM_VERSION = Gem::Version.new("7.0.0") + + register_as :knapsack + + def self.version + Gem.loaded_specs["knapsack_pro"]&.version + end + + def self.loaded? + !defined?(::KnapsackPro).nil? && !defined?(::KnapsackPro::Extensions::RSpecExtension).nil? && + !defined?(::KnapsackPro::Extensions::RSpecExtension::Runner).nil? + end + + def self.compatible? + super && version >= MINIMUM_VERSION + end + + # test environments should not auto instrument test libraries + def auto_instrument? + false + end + + # TODO: not every integration needs a configuration + def new_configuration + Integration::Configuration.new(true) + end + + def patcher + Patcher + end + end + end + end + end +end diff --git a/lib/datadog/ci/contrib/knapsack/patcher.rb b/lib/datadog/ci/contrib/knapsack/patcher.rb new file mode 100644 index 00000000..f6265919 --- /dev/null +++ b/lib/datadog/ci/contrib/knapsack/patcher.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require_relative "../patcher" + +module Datadog + module CI + module Contrib + module Knapsack + module Patcher + include Datadog::CI::Contrib::Patcher + + module_function + + def target_version + Integration.version + end + + def patch + if ::RSpec::Core::Runner.ancestors.include?(::KnapsackPro::Extensions::RSpecExtension::Runner) + # knapsack already patched rspec runner + require_relative "runner" + ::RSpec::Core::Runner.include(Datadog::CI::Contrib::Knapsack::Runner) + else + # knapsack didn't patch rspec runner yet + require_relative "extension" + ::KnapsackPro::Extensions::RSpecExtension.include(Datadog::CI::Contrib::Knapsack::Extension) + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/contrib/knapsack/runner.rb b/lib/datadog/ci/contrib/knapsack/runner.rb new file mode 100644 index 00000000..6b2003fc --- /dev/null +++ b/lib/datadog/ci/contrib/knapsack/runner.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require_relative "../../../ext/test" +require_relative "../ext" + +module Datadog + module CI + module Contrib + module Knapsack + module Runner + def self.included(base) + base.prepend(InstanceMethods) + end + + module InstanceMethods + # TODO: this is coupled to RSpec integration being present + def knapsack__run_specs(*args) + return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled] + return super unless datadog_configuration[:enabled] + + test_session = test_visibility_component.start_test_session( + tags: { + CI::Ext::Test::TAG_FRAMEWORK => CI::Contrib::RSpec::Ext::FRAMEWORK, + CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::RSpec::Integration.version.to_s + }, + service: datadog_configuration[:service_name] + ) + + test_module = test_visibility_component.start_test_module(CI::Contrib::RSpec::Ext::FRAMEWORK) + + result = super + return result unless test_module && test_session + + if result != 0 + test_module.failed! + test_session.failed! + else + test_module.passed! + test_session.passed! + end + test_module.finish + test_session.finish + + result + end + + private + + def datadog_configuration + Datadog.configuration.ci[:rspec] + end + + def test_visibility_component + Datadog.send(:components).test_visibility + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/contrib/rspec/knapsack_pro/extension.rb b/lib/datadog/ci/contrib/rspec/knapsack_pro/extension.rb deleted file mode 100644 index f7fe2dcd..00000000 --- a/lib/datadog/ci/contrib/rspec/knapsack_pro/extension.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -require "knapsack_pro/extensions/rspec_extension" - -require_relative "runner" - -module Datadog - module CI - module Contrib - module RSpec - module KnapsackPro - module Extension - def self.included(base) - base.singleton_class.prepend(ClassMethods) - end - - module ClassMethods - def setup! - super - - ::RSpec::Core::Runner.include(Datadog::CI::Contrib::RSpec::KnapsackPro::Runner) - end - end - end - end - end - end - end -end diff --git a/lib/datadog/ci/contrib/rspec/knapsack_pro/patcher.rb b/lib/datadog/ci/contrib/rspec/knapsack_pro/patcher.rb deleted file mode 100644 index 3346f6da..00000000 --- a/lib/datadog/ci/contrib/rspec/knapsack_pro/patcher.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -module Datadog - module CI - module Contrib - module RSpec - module KnapsackPro - module Patcher - def self.patch - if defined?(::KnapsackPro::Extensions::RSpecExtension::Runner) && - ::RSpec::Core::Runner.ancestors.include?(::KnapsackPro::Extensions::RSpecExtension::Runner) - # knapsack already patched rspec runner - require_relative "runner" - ::RSpec::Core::Runner.include(KnapsackPro::Runner) - else - # knapsack didn't patch rspec runner yet - require_relative "extension" - ::KnapsackPro::Extensions::RSpecExtension.include(KnapsackPro::Extension) - end - end - end - end - end - end - end -end diff --git a/lib/datadog/ci/contrib/rspec/knapsack_pro/runner.rb b/lib/datadog/ci/contrib/rspec/knapsack_pro/runner.rb deleted file mode 100644 index c4a386f1..00000000 --- a/lib/datadog/ci/contrib/rspec/knapsack_pro/runner.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -require_relative "../../../ext/test" -require_relative "../ext" - -module Datadog - module CI - module Contrib - module RSpec - module KnapsackPro - module Runner - def self.included(base) - base.prepend(InstanceMethods) - end - - module InstanceMethods - def knapsack__run_specs(*args) - return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled] - return super unless datadog_configuration[:enabled] - - test_session = test_visibility_component.start_test_session( - tags: { - CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK, - CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::RSpec::Integration.version.to_s - }, - service: datadog_configuration[:service_name] - ) - - test_module = test_visibility_component.start_test_module(Ext::FRAMEWORK) - - result = super - return result unless test_module && test_session - - if result != 0 - test_module.failed! - test_session.failed! - else - test_module.passed! - test_session.passed! - end - test_module.finish - test_session.finish - - result - end - - private - - def datadog_configuration - Datadog.configuration.ci[:rspec] - end - - def test_visibility_component - Datadog.send(:components).test_visibility - end - end - end - end - end - end - end -end diff --git a/lib/datadog/ci/contrib/rspec/patcher.rb b/lib/datadog/ci/contrib/rspec/patcher.rb index 145ed035..fa698d83 100644 --- a/lib/datadog/ci/contrib/rspec/patcher.rb +++ b/lib/datadog/ci/contrib/rspec/patcher.rb @@ -27,13 +27,6 @@ def patch ::RSpec::Queue::Runner.include(Runner) end - if knapsack_pro? - # Knapsack Pro test runner instrumentation - # https://github.com/KnapsackPro/knapsack_pro-ruby - require_relative "knapsack_pro/patcher" - Datadog::CI::Contrib::RSpec::KnapsackPro::Patcher.patch - end - # default rspec test runner instrumentation ::RSpec::Core::Runner.include(Runner) @@ -44,14 +37,6 @@ def patch def ci_queue? !!defined?(::RSpec::Queue::Runner) end - - def knapsack_pro? - knapsack_version = Gem.loaded_specs["knapsack_pro"]&.version - - # additional instrumentation is needed for KnapsackPro version 7 and later - !!defined?(::KnapsackPro) && - !knapsack_version.nil? && knapsack_version >= Gem::Version.new("7") - end end end end diff --git a/sig/datadog/ci/contrib/knapsack/extension.rbs b/sig/datadog/ci/contrib/knapsack/extension.rbs new file mode 100644 index 00000000..92576fec --- /dev/null +++ b/sig/datadog/ci/contrib/knapsack/extension.rbs @@ -0,0 +1,17 @@ +module Datadog + module CI + module Contrib + module Knapsack + module Extension + def self.included: (untyped base) -> untyped + + module ClassMethods + include ::KnapsackPro::Extensions::RSpecExtension + + def setup!: () -> void + end + end + end + end + end +end diff --git a/sig/datadog/ci/contrib/knapsack/integration.rbs b/sig/datadog/ci/contrib/knapsack/integration.rbs new file mode 100644 index 00000000..cf19b5b7 --- /dev/null +++ b/sig/datadog/ci/contrib/knapsack/integration.rbs @@ -0,0 +1,30 @@ +module Datadog + module CI + module Contrib + module Knapsack + class Integration + extend Datadog::CI::Contrib::Integration::ClassMethods + include Datadog::CI::Contrib::Integration::InstanceMethods + + class Configuration + def initialize: (bool enabled) -> void + end + + MINIMUM_VERSION: Gem::Version + + def self.version: () -> untyped + + def self.loaded?: () -> bool + + def self.compatible?: () -> bool + + def auto_instrument?: () -> bool + + def new_configuration: () -> Configuration + + def patcher: () -> singleton(Patcher) + end + end + end + end +end diff --git a/sig/datadog/ci/contrib/knapsack/patcher.rbs b/sig/datadog/ci/contrib/knapsack/patcher.rbs new file mode 100644 index 00000000..48ef1611 --- /dev/null +++ b/sig/datadog/ci/contrib/knapsack/patcher.rbs @@ -0,0 +1,11 @@ +module Datadog + module CI + module Contrib + module Knapsack + module Patcher + def self.patch: () -> void + end + end + end + end +end diff --git a/sig/datadog/ci/contrib/knapsack/runner.rbs b/sig/datadog/ci/contrib/knapsack/runner.rbs new file mode 100644 index 00000000..f51cbb99 --- /dev/null +++ b/sig/datadog/ci/contrib/knapsack/runner.rbs @@ -0,0 +1,23 @@ +module Datadog + module CI + module Contrib + module Knapsack + module Runner + def self.included: (untyped base) -> untyped + + module InstanceMethods + include ::KnapsackPro::Runners::Queue::RSpecRunner + + def knapsack__run_specs: (untyped args) -> untyped + + private + + def datadog_configuration: () -> untyped + + def test_visibility_component: () -> Datadog::CI::TestVisibility::Component + end + end + end + end + end +end diff --git a/sig/datadog/ci/contrib/rspec/knapsack_pro/extension.rbs b/sig/datadog/ci/contrib/rspec/knapsack_pro/extension.rbs deleted file mode 100644 index f0aa363c..00000000 --- a/sig/datadog/ci/contrib/rspec/knapsack_pro/extension.rbs +++ /dev/null @@ -1,19 +0,0 @@ -module Datadog - module CI - module Contrib - module RSpec - module KnapsackPro - module Extension - def self.included: (untyped base) -> untyped - - module ClassMethods - include ::KnapsackPro::Extensions::RSpecExtension - - def setup!: () -> void - end - end - end - end - end - end -end diff --git a/sig/datadog/ci/contrib/rspec/knapsack_pro/patcher.rbs b/sig/datadog/ci/contrib/rspec/knapsack_pro/patcher.rbs deleted file mode 100644 index 11d34d86..00000000 --- a/sig/datadog/ci/contrib/rspec/knapsack_pro/patcher.rbs +++ /dev/null @@ -1,13 +0,0 @@ -module Datadog - module CI - module Contrib - module RSpec - module KnapsackPro - module Patcher - def self.patch: () -> void - end - end - end - end - end -end diff --git a/sig/datadog/ci/contrib/rspec/knapsack_pro/runner.rbs b/sig/datadog/ci/contrib/rspec/knapsack_pro/runner.rbs deleted file mode 100644 index f850b66e..00000000 --- a/sig/datadog/ci/contrib/rspec/knapsack_pro/runner.rbs +++ /dev/null @@ -1,25 +0,0 @@ -module Datadog - module CI - module Contrib - module RSpec - module KnapsackPro - module Runner - def self.included: (untyped base) -> untyped - - module InstanceMethods - include ::KnapsackPro::Runners::Queue::RSpecRunner - - def knapsack__run_specs: (untyped args) -> untyped - - private - - def datadog_configuration: () -> untyped - - def test_visibility_component: () -> Datadog::CI::TestVisibility::Component - end - end - end - end - end - end -end From 4905314b9772201e393af7d5efba5a0e4dbd606c Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 8 Nov 2024 14:24:54 +0100 Subject: [PATCH 2/7] introduce Datadog::CI::Contrib::Instrumentation module to extract the logic from Settings --- lib/datadog/ci/configuration/settings.rb | 24 ++------------ lib/datadog/ci/contrib/instrumentation.rb | 32 +++++++++++++++++++ lib/datadog/ci/contrib/knapsack/runner.rb | 2 +- sig/datadog/ci/contrib/instrumentation.rbs | 14 ++++++++ .../datadog/ci/configuration/settings_spec.rb | 2 +- 5 files changed, 51 insertions(+), 23 deletions(-) create mode 100644 lib/datadog/ci/contrib/instrumentation.rb create mode 100644 sig/datadog/ci/contrib/instrumentation.rbs diff --git a/lib/datadog/ci/configuration/settings.rb b/lib/datadog/ci/configuration/settings.rb index 8212913a..45188bc3 100644 --- a/lib/datadog/ci/configuration/settings.rb +++ b/lib/datadog/ci/configuration/settings.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require_relative "../contrib/instrumentation" require_relative "../ext/settings" require_relative "../utils/bundle" @@ -8,8 +9,6 @@ module CI module Configuration # Adds CI behavior to ddtrace settings module Settings - InvalidIntegrationError = Class.new(StandardError) - def self.extended(base) base = base.singleton_class unless base.is_a?(Class) add_settings!(base) @@ -126,23 +125,11 @@ def self.add_settings!(base) define_method(:instrument) do |integration_name, options = {}, &block| return unless enabled - integration = fetch_integration(integration_name) - integration.configure(options, &block) - - return unless integration.enabled - - patch_results = integration.patch - next if patch_results == true - - error_message = <<-ERROR - Available?: #{patch_results[:available]}, Loaded?: #{patch_results[:loaded]}, - Compatible?: #{patch_results[:compatible]}, Patchable?: #{patch_results[:patchable]}" - ERROR - Datadog.logger.warn("Unable to patch #{integration_name} (#{error_message})") + Contrib::Instrumentation.instrument(integration_name, options, &block) end define_method(:[]) do |integration_name| - fetch_integration(integration_name).configuration + Contrib::Instrumentation.fetch_integration(integration_name).configuration end option :trace_flush @@ -151,11 +138,6 @@ def self.add_settings!(base) o.type :hash o.default({}) end - - define_method(:fetch_integration) do |name| - Datadog::CI::Contrib::Integration.registry[name] || - raise(InvalidIntegrationError, "'#{name}' is not a valid integration.") - end end end end diff --git a/lib/datadog/ci/contrib/instrumentation.rb b/lib/datadog/ci/contrib/instrumentation.rb new file mode 100644 index 00000000..21ab384f --- /dev/null +++ b/lib/datadog/ci/contrib/instrumentation.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Datadog + module CI + module Contrib + module Instrumentation + class InvalidIntegrationError < StandardError; end + + def self.instrument(integration_name, options = {}, &block) + integration = fetch_integration(integration_name) + integration.configure(options, &block) + + return unless integration.enabled + + patch_results = integration.patch + return if patch_results == true + + error_message = <<-ERROR + Available?: #{patch_results[:available]}, Loaded?: #{patch_results[:loaded]}, + Compatible?: #{patch_results[:compatible]}, Patchable?: #{patch_results[:patchable]}" + ERROR + Datadog.logger.warn("Unable to patch #{integration_name} (#{error_message})") + end + + def self.fetch_integration(name) + Datadog::CI::Contrib::Integration.registry[name] || + raise(InvalidIntegrationError, "'#{name}' is not a valid integration.") + end + end + end + end +end diff --git a/lib/datadog/ci/contrib/knapsack/runner.rb b/lib/datadog/ci/contrib/knapsack/runner.rb index 6b2003fc..fe80db8e 100644 --- a/lib/datadog/ci/contrib/knapsack/runner.rb +++ b/lib/datadog/ci/contrib/knapsack/runner.rb @@ -13,7 +13,7 @@ def self.included(base) end module InstanceMethods - # TODO: this is coupled to RSpec integration being present + # TODO: this is coupled to RSpec integration being present, not sure if it's bad or not at this point def knapsack__run_specs(*args) return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled] return super unless datadog_configuration[:enabled] diff --git a/sig/datadog/ci/contrib/instrumentation.rbs b/sig/datadog/ci/contrib/instrumentation.rbs new file mode 100644 index 00000000..958c9862 --- /dev/null +++ b/sig/datadog/ci/contrib/instrumentation.rbs @@ -0,0 +1,14 @@ +module Datadog + module CI + module Contrib + module Instrumentation + class InvalidIntegrationError < StandardError + end + + def self.instrument: (Symbol integration_name, ?::Hash[untyped, untyped] options) { (?) -> untyped } -> void + + def self.fetch_integration: (Symbol name) -> untyped + end + end + end +end diff --git a/spec/datadog/ci/configuration/settings_spec.rb b/spec/datadog/ci/configuration/settings_spec.rb index 9a8fe759..20f83739 100644 --- a/spec/datadog/ci/configuration/settings_spec.rb +++ b/spec/datadog/ci/configuration/settings_spec.rb @@ -657,7 +657,7 @@ def patcher let(:integration_name) { :not_existing } it "does not patch the integration" do - expect { instrument }.to raise_error(Datadog::CI::Configuration::Settings::InvalidIntegrationError) + expect { instrument }.to raise_error(Datadog::CI::Contrib::Instrumentation::InvalidIntegrationError) end end From c898a265db9d9c5c597c3bb539f9c9021d3f5b31 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 14 Nov 2024 12:06:29 +0100 Subject: [PATCH 3/7] refactor Datadog::CI::Contrib::Integration module and integrations registry --- lib/datadog/ci.rb | 9 +- lib/datadog/ci/contrib/cucumber/formatter.rb | 15 +- .../ci/contrib/cucumber/integration.rb | 12 +- lib/datadog/ci/contrib/cucumber/patcher.rb | 4 - lib/datadog/ci/contrib/integration.rb | 213 ++++++++---------- .../ci/contrib/knapsack/integration.rb | 19 +- lib/datadog/ci/contrib/knapsack/patcher.rb | 4 - lib/datadog/ci/contrib/knapsack/runner.rb | 7 +- .../ci/contrib/minitest/integration.rb | 12 +- lib/datadog/ci/contrib/minitest/patcher.rb | 4 - lib/datadog/ci/contrib/minitest/runner.rb | 7 +- lib/datadog/ci/contrib/minitest/test.rb | 7 +- lib/datadog/ci/contrib/rspec/example.rb | 7 +- lib/datadog/ci/contrib/rspec/integration.rb | 12 +- lib/datadog/ci/contrib/rspec/patcher.rb | 5 +- lib/datadog/ci/contrib/rspec/runner.rb | 7 +- .../ci/contrib/selenium/integration.rb | 12 +- lib/datadog/ci/contrib/selenium/navigation.rb | 6 +- lib/datadog/ci/contrib/selenium/patcher.rb | 4 - .../ci/contrib/simplecov/integration.rb | 12 +- lib/datadog/ci/contrib/simplecov/patcher.rb | 4 - sig/datadog/ci/contrib/cucumber/formatter.rbs | 4 +- .../ci/contrib/cucumber/integration.rbs | 11 +- sig/datadog/ci/contrib/cucumber/patcher.rbs | 2 - sig/datadog/ci/contrib/integration.rbs | 37 ++- .../ci/contrib/knapsack/integration.rbs | 17 +- sig/datadog/ci/contrib/knapsack/runner.rbs | 2 + .../ci/contrib/minitest/integration.rbs | 9 +- sig/datadog/ci/contrib/minitest/patcher.rbs | 2 - sig/datadog/ci/contrib/minitest/runner.rbs | 2 + sig/datadog/ci/contrib/minitest/test.rbs | 2 + sig/datadog/ci/contrib/rspec/example.rbs | 1 + sig/datadog/ci/contrib/rspec/integration.rbs | 9 +- sig/datadog/ci/contrib/rspec/patcher.rbs | 2 - sig/datadog/ci/contrib/rspec/runner.rbs | 2 + .../ci/contrib/selenium/integration.rbs | 11 +- .../ci/contrib/selenium/navigation.rbs | 2 + sig/datadog/ci/contrib/selenium/patcher.rbs | 2 - .../ci/contrib/simplecov/integration.rbs | 11 +- sig/datadog/ci/contrib/simplecov/patcher.rbs | 2 - .../datadog/ci/configuration/settings_spec.rb | 77 +++---- .../contrib/cucumber/instrumentation_spec.rb | 21 +- .../ci/contrib/cucumber/integration_spec.rb | 6 +- .../knapsack_rspec/instrumentation_spec.rb | 5 + .../knapsack_rspec_go/instrumentation_spec.rb | 5 + .../contrib/minitest/instrumentation_spec.rb | 16 +- .../ci/contrib/minitest/integration_spec.rb | 6 +- .../ci/contrib/rspec/instrumentation_spec.rb | 16 +- .../ci/contrib/rspec/integration_spec.rb | 6 +- 49 files changed, 313 insertions(+), 357 deletions(-) diff --git a/lib/datadog/ci.rb b/lib/datadog/ci.rb index 7d1828fa..a3ddfd04 100644 --- a/lib/datadog/ci.rb +++ b/lib/datadog/ci.rb @@ -406,9 +406,16 @@ def test_optimisation end # Integrations + +# Test frameworks (manual instrumentation) require_relative "ci/contrib/cucumber/integration" -require_relative "ci/contrib/rspec/integration" require_relative "ci/contrib/minitest/integration" +require_relative "ci/contrib/rspec/integration" + +# Test runners (instrumented automatically when corresponding frameworks are instrumented) +require_relative "ci/contrib/knapsack/integration" + +# Additional test libraries (auto instrumented later on test session start) require_relative "ci/contrib/selenium/integration" require_relative "ci/contrib/simplecov/integration" diff --git a/lib/datadog/ci/contrib/cucumber/formatter.rb b/lib/datadog/ci/contrib/cucumber/formatter.rb index 95d7aa4d..456588a2 100644 --- a/lib/datadog/ci/contrib/cucumber/formatter.rb +++ b/lib/datadog/ci/contrib/cucumber/formatter.rb @@ -3,6 +3,7 @@ require_relative "../../ext/test" require_relative "../../git/local_repository" require_relative "../../utils/test_run" +require_relative "../instrumentation" require_relative "ext" module Datadog @@ -38,9 +39,9 @@ def on_test_run_started(event) test_visibility_component.start_test_session( tags: { CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK, - CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Cucumber::Integration.version.to_s + CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s }, - service: configuration[:service_name] + service: datadog_configuration[:service_name] ) test_visibility_component.start_test_module(Ext::FRAMEWORK) end @@ -61,7 +62,7 @@ def on_test_case_started(event) # @type var tags: Hash[String, String] tags = { CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK, - CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Cucumber::Integration.version.to_s, + CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s, CI::Ext::Test::TAG_SOURCE_FILE => Git::LocalRepository.relative_to_root(event.test_case.location.file), CI::Ext::Test::TAG_SOURCE_START => event.test_case.location.line.to_s } @@ -81,7 +82,7 @@ def on_test_case_started(event) event.test_case.name, test_suite_name, tags: tags, - service: configuration[:service_name] + service: datadog_configuration[:service_name] ) if event.test_case.match_tags?("@#{CI::Ext::Test::ITR_UNSKIPPABLE_OPTION}") test_span&.itr_unskippable! @@ -199,7 +200,11 @@ def ok?(result, strict) end end - def configuration + def datadog_integration + CI::Contrib::Instrumentation.fetch_integration(:cucumber) + end + + def datadog_configuration Datadog.configuration.ci[:cucumber] end diff --git a/lib/datadog/ci/contrib/cucumber/integration.rb b/lib/datadog/ci/contrib/cucumber/integration.rb index bbbcf82c..607c5530 100644 --- a/lib/datadog/ci/contrib/cucumber/integration.rb +++ b/lib/datadog/ci/contrib/cucumber/integration.rb @@ -9,22 +9,18 @@ module CI module Contrib module Cucumber # Description of Cucumber integration - class Integration - include Datadog::CI::Contrib::Integration - + class Integration < Contrib::Integration MINIMUM_VERSION = Gem::Version.new("3.0.0") - register_as :cucumber - - def self.version + def version Gem.loaded_specs["cucumber"]&.version end - def self.loaded? + def loaded? !defined?(::Cucumber).nil? && !defined?(::Cucumber::Runtime).nil? end - def self.compatible? + def compatible? super && version >= MINIMUM_VERSION end diff --git a/lib/datadog/ci/contrib/cucumber/patcher.rb b/lib/datadog/ci/contrib/cucumber/patcher.rb index af130550..eab97909 100644 --- a/lib/datadog/ci/contrib/cucumber/patcher.rb +++ b/lib/datadog/ci/contrib/cucumber/patcher.rb @@ -14,10 +14,6 @@ module Patcher module_function - def target_version - Integration.version - end - def patch ::Cucumber::Runtime.include(Instrumentation) end diff --git a/lib/datadog/ci/contrib/integration.rb b/lib/datadog/ci/contrib/integration.rb index 24021ae9..c8056da5 100644 --- a/lib/datadog/ci/contrib/integration.rb +++ b/lib/datadog/ci/contrib/integration.rb @@ -5,143 +5,130 @@ module Datadog module CI module Contrib - module Integration + class Integration @registry = {} - def self.included(base) - base.extend(ClassMethods) - base.include(InstanceMethods) + def self.inherited(subclass) + @registry[integration_name(subclass)] = subclass.new end - def self.register(klass, name) - registry[name] = klass.new + # take the parent module name and downcase it + # for example for Datadog::CI::Contrib::RSpec::Integration it will be :rspec + def self.integration_name(subclass) + result = subclass.name&.split("::")&.[](-2)&.downcase&.to_sym + raise "Integration name could not be derived for #{subclass}" if result.nil? + result end def self.registry @registry end - # Class-level methods for Integration - module ClassMethods - def register_as(name) - Integration.register(self, name) - end - - # Version of the integration target code in the environment. - # - # This is the gem version, when the instrumentation target is a Ruby gem. - # - # If the target for instrumentation has concept of versioning, override {.version}, - # otherwise override {.available?} and implement a custom target presence check. - # @return [Object] the target version - def version - nil - end + # Version of the integration target code in the environment. + # + # This is the gem version, when the instrumentation target is a Ruby gem. + # + # If the target for instrumentation has concept of versioning, override {.version}, + # otherwise override {.available?} and implement a custom target presence check. + # @return [Object] the target version + def version + nil + end - # Is the target available to be instrumented? (e.g. gem installed?) - # - # The target doesn't have to be loaded (e.g. `require`) yet, but needs to be able - # to be loaded before instrumentation can commence. - # - # By default, {.available?} checks if {.version} returned a non-nil object. - # - # If the target for instrumentation has concept of versioning, override {.version}, - # otherwise override {.available?} and implement a custom target presence check. - # @return [Boolean] is the target available for instrumentation in this Ruby environment? - def available? - !version.nil? - end + # Is the target available to be instrumented? (e.g. gem installed?) + # + # The target doesn't have to be loaded (e.g. `require`) yet, but needs to be able + # to be loaded before instrumentation can commence. + # + # By default, {.available?} checks if {.version} returned a non-nil object. + # + # If the target for instrumentation has concept of versioning, override {.version}, + # otherwise override {.available?} and implement a custom target presence check. + # @return [Boolean] is the target available for instrumentation in this Ruby environment? + def available? + !version.nil? + end - # Is the target loaded into the application? (e.g. gem required? Constant defined?) - # - # The target's objects should be ready to be referenced by the instrumented when {.loaded} - # returns `true`. - # - # @return [Boolean] is the target ready to be referenced during instrumentation? - def loaded? - true - end + # Is the target loaded into the application? (e.g. gem required? Constant defined?) + # + # The target's objects should be ready to be referenced by the instrumented when {.loaded} + # returns `true`. + # + # @return [Boolean] is the target ready to be referenced during instrumentation? + def loaded? + true + end - # Is this instrumentation compatible with the available target? (e.g. minimum version met?) - # @return [Boolean] is the available target compatible with this instrumentation? - def compatible? - available? - end + # Is this instrumentation compatible with the available target? (e.g. minimum version met?) + # @return [Boolean] is the available target compatible with this instrumentation? + def compatible? + available? + end - # Can the patch for this integration be applied? - # - # By default, this is equivalent to {#available?}, {#loaded?}, and {#compatible?} - # all being truthy. - def patchable? - available? && loaded? && compatible? - end + # Can the patch for this integration be applied? + # + # By default, this is equivalent to {#available?}, {#loaded?}, and {#compatible?} + # all being truthy. + def patchable? + available? && loaded? && compatible? end - module InstanceMethods - # returns the configuration instance. - def configuration - @configuration ||= new_configuration - end + # returns the configuration instance. + def configuration + @configuration ||= new_configuration + end - def configure(options = {}, &block) - configuration.configure(options, &block) - configuration - end + def configure(options = {}, &block) + configuration.configure(options, &block) + configuration + end - # Resets all configuration options - def reset_configuration! - @configuration = nil - end + def enabled + configuration.enabled + end - def enabled - configuration.enabled - end + # The patcher module to inject instrumented objects into the instrumentation target. + # + # {Contrib::Patcher} includes the basic functionality of a patcher. `include`ing + # {Contrib::Patcher} into a new module is the recommend way to create a custom patcher. + # + # @return [Contrib::Patcher] a module that `include`s {Contrib::Patcher} + def patcher + nil + end - # The patcher module to inject instrumented objects into the instrumentation target. - # - # {Contrib::Patcher} includes the basic functionality of a patcher. `include`ing - # {Contrib::Patcher} into a new module is the recommend way to create a custom patcher. - # - # @return [Contrib::Patcher] a module that `include`s {Contrib::Patcher} - def patcher - nil + # @!visibility private + def patch + # @type var patcher_klass: untyped + patcher_klass = patcher + if !patchable? || patcher_klass.nil? + return { + available: available?, + loaded: loaded?, + compatible: compatible?, + patchable: patchable? + } end - # @!visibility private - def patch - # @type var patcher_klass: untyped - patcher_klass = patcher - if !self.class.patchable? || patcher_klass.nil? - return { - available: self.class.available?, - loaded: self.class.loaded?, - compatible: self.class.compatible?, - patchable: self.class.patchable? - } - end - - patcher_klass.patch - true - end + patcher_klass.patch + true + end - # Can the patch for this integration be applied automatically? - # @return [Boolean] can the tracer activate this instrumentation without explicit user input? - def auto_instrument? - true - end + # Can the patch for this integration be applied automatically? + # @return [Boolean] can the tracer activate this instrumentation without explicit user input? + def auto_instrument? + true + end - protected - - # Returns a new configuration object for this integration. - # - # This method normally needs to be overridden for each integration - # as their settings, defaults and environment variables are - # specific for each integration. - # - # @return [Datadog::CI::Contrib::Settings] a new, integration-specific settings object - def new_configuration - Datadog::CI::Contrib::Settings.new - end + # Returns a new configuration object for this integration. + # + # This method normally needs to be overridden for each integration + # as their settings, defaults and environment variables are + # specific for each integration. + # + # @return [Datadog::CI::Contrib::Settings] a new, integration-specific settings object + def new_configuration + Datadog::CI::Contrib::Settings.new end end end diff --git a/lib/datadog/ci/contrib/knapsack/integration.rb b/lib/datadog/ci/contrib/knapsack/integration.rb index e41cc5af..838a967d 100644 --- a/lib/datadog/ci/contrib/knapsack/integration.rb +++ b/lib/datadog/ci/contrib/knapsack/integration.rb @@ -9,25 +9,19 @@ module Contrib module Knapsack # Knapsack Pro test runner instrumentation # https://github.com/KnapsackPro/knapsack_pro-ruby - class Integration - include Datadog::CI::Contrib::Integration - - Configuration = Struct.new(:enabled) - + class Integration < Contrib::Integration MINIMUM_VERSION = Gem::Version.new("7.0.0") - register_as :knapsack - - def self.version + def version Gem.loaded_specs["knapsack_pro"]&.version end - def self.loaded? + def loaded? !defined?(::KnapsackPro).nil? && !defined?(::KnapsackPro::Extensions::RSpecExtension).nil? && !defined?(::KnapsackPro::Extensions::RSpecExtension::Runner).nil? end - def self.compatible? + def compatible? super && version >= MINIMUM_VERSION end @@ -36,11 +30,6 @@ def auto_instrument? false end - # TODO: not every integration needs a configuration - def new_configuration - Integration::Configuration.new(true) - end - def patcher Patcher end diff --git a/lib/datadog/ci/contrib/knapsack/patcher.rb b/lib/datadog/ci/contrib/knapsack/patcher.rb index f6265919..b8ba5db6 100644 --- a/lib/datadog/ci/contrib/knapsack/patcher.rb +++ b/lib/datadog/ci/contrib/knapsack/patcher.rb @@ -11,10 +11,6 @@ module Patcher module_function - def target_version - Integration.version - end - def patch if ::RSpec::Core::Runner.ancestors.include?(::KnapsackPro::Extensions::RSpecExtension::Runner) # knapsack already patched rspec runner diff --git a/lib/datadog/ci/contrib/knapsack/runner.rb b/lib/datadog/ci/contrib/knapsack/runner.rb index fe80db8e..9e399a22 100644 --- a/lib/datadog/ci/contrib/knapsack/runner.rb +++ b/lib/datadog/ci/contrib/knapsack/runner.rb @@ -2,6 +2,7 @@ require_relative "../../../ext/test" require_relative "../ext" +require_relative "../instrumentation" module Datadog module CI @@ -21,7 +22,7 @@ def knapsack__run_specs(*args) test_session = test_visibility_component.start_test_session( tags: { CI::Ext::Test::TAG_FRAMEWORK => CI::Contrib::RSpec::Ext::FRAMEWORK, - CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::RSpec::Integration.version.to_s + CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s }, service: datadog_configuration[:service_name] ) @@ -46,6 +47,10 @@ def knapsack__run_specs(*args) private + def datadog_integration + CI::Contrib::Instrumentation.fetch_integration(:rspec) + end + def datadog_configuration Datadog.configuration.ci[:rspec] end diff --git a/lib/datadog/ci/contrib/minitest/integration.rb b/lib/datadog/ci/contrib/minitest/integration.rb index ce1cd89c..9c98b8a7 100644 --- a/lib/datadog/ci/contrib/minitest/integration.rb +++ b/lib/datadog/ci/contrib/minitest/integration.rb @@ -9,22 +9,18 @@ module CI module Contrib module Minitest # Description of Minitest integration - class Integration - include Datadog::CI::Contrib::Integration - + class Integration < Contrib::Integration MINIMUM_VERSION = Gem::Version.new("5.0.0") - register_as :minitest - - def self.version + def version Gem.loaded_specs["minitest"]&.version end - def self.loaded? + def loaded? !defined?(::Minitest).nil? end - def self.compatible? + def compatible? super && version >= MINIMUM_VERSION end diff --git a/lib/datadog/ci/contrib/minitest/patcher.rb b/lib/datadog/ci/contrib/minitest/patcher.rb index de98d603..159409b0 100644 --- a/lib/datadog/ci/contrib/minitest/patcher.rb +++ b/lib/datadog/ci/contrib/minitest/patcher.rb @@ -15,10 +15,6 @@ module Patcher module_function - def target_version - Integration.version - end - def patch # test session start ::Minitest.include(Runner) diff --git a/lib/datadog/ci/contrib/minitest/runner.rb b/lib/datadog/ci/contrib/minitest/runner.rb index e8ecae2b..edc753d4 100644 --- a/lib/datadog/ci/contrib/minitest/runner.rb +++ b/lib/datadog/ci/contrib/minitest/runner.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative "../../ext/test" +require_relative "../instrumentation" require_relative "ext" module Datadog @@ -25,7 +26,7 @@ def init_plugins(*args) test_visibility_component.start_test_session( tags: { CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK, - CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Minitest::Integration.version.to_s + CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s }, service: datadog_configuration[:service_name], total_tests_count: (DD_ESTIMATED_TESTS_PER_SUITE * ::Minitest::Runnable.runnables.size).to_i @@ -47,6 +48,10 @@ def run_one_method(klass, method_name) private + def datadog_integration + CI::Contrib::Instrumentation.fetch_integration(:minitest) + end + def datadog_configuration Datadog.configuration.ci[:minitest] end diff --git a/lib/datadog/ci/contrib/minitest/test.rb b/lib/datadog/ci/contrib/minitest/test.rb index 213825bb..3fb59496 100644 --- a/lib/datadog/ci/contrib/minitest/test.rb +++ b/lib/datadog/ci/contrib/minitest/test.rb @@ -2,6 +2,7 @@ require_relative "../../ext/test" require_relative "../../git/local_repository" +require_relative "../instrumentation" require_relative "ext" require_relative "helpers" @@ -36,7 +37,7 @@ def before_setup test_suite_name, tags: { CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK, - CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Minitest::Integration.version.to_s, + CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s, CI::Ext::Test::TAG_SOURCE_FILE => Git::LocalRepository.relative_to_root(source_file), CI::Ext::Test::TAG_SOURCE_START => line_number.to_s }, @@ -79,6 +80,10 @@ def finish_with_result(span, result_code) span.finish end + def datadog_integration + CI::Contrib::Instrumentation.fetch_integration(:minitest) + end + def datadog_configuration Datadog.configuration.ci[:minitest] end diff --git a/lib/datadog/ci/contrib/rspec/example.rb b/lib/datadog/ci/contrib/rspec/example.rb index e7a2ac2e..b060f549 100644 --- a/lib/datadog/ci/contrib/rspec/example.rb +++ b/lib/datadog/ci/contrib/rspec/example.rb @@ -3,6 +3,7 @@ require_relative "../../ext/test" require_relative "../../git/local_repository" require_relative "../../utils/test_run" +require_relative "../instrumentation" require_relative "ext" module Datadog @@ -46,7 +47,7 @@ def run(*args) suite_name, tags: { CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK, - CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::RSpec::Integration.version.to_s, + CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s, CI::Ext::Test::TAG_SOURCE_FILE => Git::LocalRepository.relative_to_root(metadata[:file_path]), CI::Ext::Test::TAG_SOURCE_START => metadata[:line_number].to_s, CI::Ext::Test::TAG_PARAMETERS => Utils::TestRun.test_parameters( @@ -120,6 +121,10 @@ def fetch_top_level_example_group res end + def datadog_integration + CI::Contrib::Instrumentation.fetch_integration(:rspec) + end + def datadog_configuration Datadog.configuration.ci[:rspec] end diff --git a/lib/datadog/ci/contrib/rspec/integration.rb b/lib/datadog/ci/contrib/rspec/integration.rb index c0279c79..bfe0b33a 100644 --- a/lib/datadog/ci/contrib/rspec/integration.rb +++ b/lib/datadog/ci/contrib/rspec/integration.rb @@ -9,23 +9,19 @@ module CI module Contrib module RSpec # Description of RSpec integration - class Integration - include Datadog::CI::Contrib::Integration - + class Integration < Contrib::Integration MINIMUM_VERSION = Gem::Version.new("3.0.0") - register_as :rspec - - def self.version + def version Gem.loaded_specs["rspec-core"]&.version end - def self.loaded? + def loaded? !defined?(::RSpec).nil? && !defined?(::RSpec::Core).nil? && !defined?(::RSpec::Core::Example).nil? end - def self.compatible? + def compatible? super && version >= MINIMUM_VERSION end diff --git a/lib/datadog/ci/contrib/rspec/patcher.rb b/lib/datadog/ci/contrib/rspec/patcher.rb index fa698d83..116d2040 100644 --- a/lib/datadog/ci/contrib/rspec/patcher.rb +++ b/lib/datadog/ci/contrib/rspec/patcher.rb @@ -16,12 +16,9 @@ module Patcher module_function - def target_version - Integration.version - end - def patch # ci-queue test runner instrumentation + # TODO: to be extracted in the next PR # https://github.com/Shopify/ci-queue if ci_queue? ::RSpec::Queue::Runner.include(Runner) diff --git a/lib/datadog/ci/contrib/rspec/runner.rb b/lib/datadog/ci/contrib/rspec/runner.rb index 49a88a10..a9505978 100644 --- a/lib/datadog/ci/contrib/rspec/runner.rb +++ b/lib/datadog/ci/contrib/rspec/runner.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative "../../ext/test" +require_relative "../instrumentation" require_relative "ext" module Datadog @@ -21,7 +22,7 @@ def run_specs(*args) test_session = test_visibility_component.start_test_session( tags: { CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK, - CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::RSpec::Integration.version.to_s + CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s }, service: datadog_configuration[:service_name], total_tests_count: ::RSpec.world.example_count @@ -47,6 +48,10 @@ def run_specs(*args) private + def datadog_integration + CI::Contrib::Instrumentation.fetch_integration(:rspec) + end + def datadog_configuration Datadog.configuration.ci[:rspec] end diff --git a/lib/datadog/ci/contrib/selenium/integration.rb b/lib/datadog/ci/contrib/selenium/integration.rb index 6d3e9bec..73f35c66 100644 --- a/lib/datadog/ci/contrib/selenium/integration.rb +++ b/lib/datadog/ci/contrib/selenium/integration.rb @@ -9,23 +9,19 @@ module CI module Contrib module Selenium # Description of Selenium integration - class Integration - include Datadog::CI::Contrib::Integration - + class Integration < Contrib::Integration MINIMUM_VERSION = Gem::Version.new("4.0.0") - register_as :selenium - - def self.version + def version Gem.loaded_specs["selenium-webdriver"]&.version end - def self.loaded? + def loaded? !defined?(::Selenium).nil? && !defined?(::Selenium::WebDriver).nil? && !defined?(::Selenium::WebDriver::Driver).nil? end - def self.compatible? + def compatible? super && version >= MINIMUM_VERSION end diff --git a/lib/datadog/ci/contrib/selenium/navigation.rb b/lib/datadog/ci/contrib/selenium/navigation.rb index 04151798..dd76b92f 100644 --- a/lib/datadog/ci/contrib/selenium/navigation.rb +++ b/lib/datadog/ci/contrib/selenium/navigation.rb @@ -43,7 +43,7 @@ def to(url) active_test.set_tag(CI::Ext::Test::TAG_BROWSER_DRIVER, "selenium") active_test.set_tag( CI::Ext::Test::TAG_BROWSER_DRIVER_VERSION, - Integration.version + datadog_integration.version ) active_test.set_tag( CI::Ext::Test::TAG_BROWSER_NAME, @@ -63,6 +63,10 @@ def to(url) private + def datadog_integration + CI::Contrib::Instrumentation.fetch_integration(:selenium) + end + def datadog_configuration Datadog.configuration.ci[:selenium] end diff --git a/lib/datadog/ci/contrib/selenium/patcher.rb b/lib/datadog/ci/contrib/selenium/patcher.rb index b4640c9c..9cf071b4 100644 --- a/lib/datadog/ci/contrib/selenium/patcher.rb +++ b/lib/datadog/ci/contrib/selenium/patcher.rb @@ -16,10 +16,6 @@ module Patcher module_function - def target_version - Integration.version - end - def patch ::Selenium::WebDriver::Driver.include(Driver) ::Selenium::WebDriver::Navigation.include(Navigation) diff --git a/lib/datadog/ci/contrib/simplecov/integration.rb b/lib/datadog/ci/contrib/simplecov/integration.rb index 6d7da3af..30c5f6a7 100644 --- a/lib/datadog/ci/contrib/simplecov/integration.rb +++ b/lib/datadog/ci/contrib/simplecov/integration.rb @@ -9,22 +9,18 @@ module CI module Contrib module Simplecov # Description of Simplecov integration - class Integration - include Datadog::CI::Contrib::Integration - + class Integration < Contrib::Integration MINIMUM_VERSION = Gem::Version.new("0.18.0") - register_as :simplecov - - def self.version + def version Gem.loaded_specs["simplecov"]&.version end - def self.loaded? + def loaded? !defined?(::SimpleCov).nil? end - def self.compatible? + def compatible? super && version >= MINIMUM_VERSION end diff --git a/lib/datadog/ci/contrib/simplecov/patcher.rb b/lib/datadog/ci/contrib/simplecov/patcher.rb index f200d894..39046c0c 100644 --- a/lib/datadog/ci/contrib/simplecov/patcher.rb +++ b/lib/datadog/ci/contrib/simplecov/patcher.rb @@ -14,10 +14,6 @@ module Patcher module_function - def target_version - Integration.version - end - def patch ::SimpleCov.include(ResultExtractor) end diff --git a/sig/datadog/ci/contrib/cucumber/formatter.rbs b/sig/datadog/ci/contrib/cucumber/formatter.rbs index 5fce73fe..5805f4e9 100644 --- a/sig/datadog/ci/contrib/cucumber/formatter.rbs +++ b/sig/datadog/ci/contrib/cucumber/formatter.rbs @@ -49,7 +49,9 @@ module Datadog def ok?: (Cucumber::Core::Test::Result result, untyped strict) -> bool - def configuration: () -> untyped + def datadog_integration: () -> Datadog::CI::Contrib::Integration + + def datadog_configuration: () -> untyped def test_visibility_component: () -> Datadog::CI::TestVisibility::Component diff --git a/sig/datadog/ci/contrib/cucumber/integration.rbs b/sig/datadog/ci/contrib/cucumber/integration.rbs index 9aeee479..2c589b1d 100644 --- a/sig/datadog/ci/contrib/cucumber/integration.rbs +++ b/sig/datadog/ci/contrib/cucumber/integration.rbs @@ -2,17 +2,14 @@ module Datadog module CI module Contrib module Cucumber - class Integration - extend Datadog::CI::Contrib::Integration::ClassMethods - include Datadog::CI::Contrib::Integration::InstanceMethods - + class Integration < Datadog::CI::Contrib::Integration MINIMUM_VERSION: Gem::Version - def self.version: () -> untyped + def version: () -> untyped - def self.loaded?: () -> bool + def loaded?: () -> bool - def self.compatible?: () -> bool + def compatible?: () -> bool def auto_instrument?: () -> false diff --git a/sig/datadog/ci/contrib/cucumber/patcher.rbs b/sig/datadog/ci/contrib/cucumber/patcher.rbs index ed30da67..c19daeb2 100644 --- a/sig/datadog/ci/contrib/cucumber/patcher.rbs +++ b/sig/datadog/ci/contrib/cucumber/patcher.rbs @@ -5,8 +5,6 @@ module Datadog module Patcher include Datadog::CI::Contrib::Patcher - def self?.target_version: () -> untyped - def self?.patch: () -> untyped end end diff --git a/sig/datadog/ci/contrib/integration.rbs b/sig/datadog/ci/contrib/integration.rbs index a7be702a..4708e3a2 100644 --- a/sig/datadog/ci/contrib/integration.rbs +++ b/sig/datadog/ci/contrib/integration.rbs @@ -1,43 +1,38 @@ module Datadog module CI module Contrib - module Integration + class Integration self.@registry: Hash[Symbol, untyped] - def self.included: (Module base) -> void - - def self.register: (untyped integration, Symbol name) -> void + def self.inherited: (untyped) -> void def self.registry: () -> Hash[Symbol, untyped] - module ClassMethods - def register_as: (Symbol name) -> void + def self.integration_name: (Class) -> Symbol + + def version: () -> untyped - def version: () -> Gem::Version? + def available?: () -> bool - def available?: () -> bool + def loaded?: () -> bool - def loaded?: () -> bool + def compatible?: () -> bool - def compatible?: () -> bool + def patchable?: () -> bool - def patchable?: () -> bool - end + def configuration: () -> Datadog::CI::Contrib::Settings - module InstanceMethods - extend ClassMethods - @configuration: Datadog::CI::Contrib::Settings? + def configure: (?::Hash[Symbol, untyped] options) ?{ (Datadog::CI::Contrib::Settings) -> Datadog::CI::Contrib::Settings } -> Datadog::CI::Contrib::Settings - def configuration: () -> Datadog::CI::Contrib::Settings + def enabled: () -> bool - def configure: (?::Hash[Symbol, untyped] options) ?{ (Datadog::CI::Contrib::Settings) -> Datadog::CI::Contrib::Settings } -> Datadog::CI::Contrib::Settings + def patcher: () -> Datadog::Tracing::Contrib::Patcher? - def reset_configuration!: () -> void + def patch: () -> (bool | Hash[Symbol, bool]) - def patcher: () -> Datadog::Tracing::Contrib::Patcher? + def auto_instrument?: () -> bool - def new_configuration: () -> Datadog::CI::Contrib::Settings - end + def new_configuration: () -> Datadog::CI::Contrib::Settings end end end diff --git a/sig/datadog/ci/contrib/knapsack/integration.rbs b/sig/datadog/ci/contrib/knapsack/integration.rbs index cf19b5b7..eca02a92 100644 --- a/sig/datadog/ci/contrib/knapsack/integration.rbs +++ b/sig/datadog/ci/contrib/knapsack/integration.rbs @@ -2,25 +2,18 @@ module Datadog module CI module Contrib module Knapsack - class Integration - extend Datadog::CI::Contrib::Integration::ClassMethods - include Datadog::CI::Contrib::Integration::InstanceMethods - - class Configuration - def initialize: (bool enabled) -> void - end - + class Integration < Datadog::CI::Contrib::Integration MINIMUM_VERSION: Gem::Version - def self.version: () -> untyped + def version: () -> untyped - def self.loaded?: () -> bool + def loaded?: () -> bool - def self.compatible?: () -> bool + def compatible?: () -> bool def auto_instrument?: () -> bool - def new_configuration: () -> Configuration + def new_configuration: () -> Datadog::CI::Contrib::Settings def patcher: () -> singleton(Patcher) end diff --git a/sig/datadog/ci/contrib/knapsack/runner.rbs b/sig/datadog/ci/contrib/knapsack/runner.rbs index f51cbb99..a8179ac6 100644 --- a/sig/datadog/ci/contrib/knapsack/runner.rbs +++ b/sig/datadog/ci/contrib/knapsack/runner.rbs @@ -12,6 +12,8 @@ module Datadog private + def datadog_integration: () -> Datadog::CI::Contrib::Integration + def datadog_configuration: () -> untyped def test_visibility_component: () -> Datadog::CI::TestVisibility::Component diff --git a/sig/datadog/ci/contrib/minitest/integration.rbs b/sig/datadog/ci/contrib/minitest/integration.rbs index ce86c736..1c10efd1 100644 --- a/sig/datadog/ci/contrib/minitest/integration.rbs +++ b/sig/datadog/ci/contrib/minitest/integration.rbs @@ -2,15 +2,12 @@ module Datadog module CI module Contrib module Minitest - class Integration - extend Datadog::CI::Contrib::Integration::ClassMethods - include Datadog::CI::Contrib::Integration::InstanceMethods - + class Integration < Datadog::CI::Contrib::Integration MINIMUM_VERSION: Gem::Version - def self.version: () -> untyped + def version: () -> untyped - def self.loaded?: () -> bool + def loaded?: () -> bool def compatible?: () -> bool diff --git a/sig/datadog/ci/contrib/minitest/patcher.rbs b/sig/datadog/ci/contrib/minitest/patcher.rbs index ab494098..c9012086 100644 --- a/sig/datadog/ci/contrib/minitest/patcher.rbs +++ b/sig/datadog/ci/contrib/minitest/patcher.rbs @@ -5,8 +5,6 @@ module Datadog module Patcher include Datadog::CI::Contrib::Patcher - def self?.target_version: () -> untyped - def self?.patch: () -> untyped end end diff --git a/sig/datadog/ci/contrib/minitest/runner.rbs b/sig/datadog/ci/contrib/minitest/runner.rbs index 80100aac..381b3368 100644 --- a/sig/datadog/ci/contrib/minitest/runner.rbs +++ b/sig/datadog/ci/contrib/minitest/runner.rbs @@ -16,6 +16,8 @@ module Datadog private + def datadog_integration: () -> Datadog::CI::Contrib::Integration + def datadog_configuration: () -> untyped def test_visibility_component: () -> Datadog::CI::TestVisibility::Component diff --git a/sig/datadog/ci/contrib/minitest/test.rbs b/sig/datadog/ci/contrib/minitest/test.rbs index 5d6e3d9e..310047c6 100644 --- a/sig/datadog/ci/contrib/minitest/test.rbs +++ b/sig/datadog/ci/contrib/minitest/test.rbs @@ -22,6 +22,8 @@ module Datadog private + def datadog_integration: () -> Datadog::CI::Contrib::Integration + def datadog_configuration: () -> untyped def test_visibility_component: () -> Datadog::CI::TestVisibility::Component diff --git a/sig/datadog/ci/contrib/rspec/example.rbs b/sig/datadog/ci/contrib/rspec/example.rbs index a374c150..d324d9bd 100644 --- a/sig/datadog/ci/contrib/rspec/example.rbs +++ b/sig/datadog/ci/contrib/rspec/example.rbs @@ -12,6 +12,7 @@ module Datadog private def fetch_top_level_example_group: () -> Hash[Symbol, untyped] + def datadog_integration: () -> Datadog::CI::Contrib::Integration def datadog_configuration: () -> untyped def test_visibility_component: () -> Datadog::CI::TestVisibility::Component def test_retries_component: () -> Datadog::CI::TestRetries::Component diff --git a/sig/datadog/ci/contrib/rspec/integration.rbs b/sig/datadog/ci/contrib/rspec/integration.rbs index c56a528f..04706988 100644 --- a/sig/datadog/ci/contrib/rspec/integration.rbs +++ b/sig/datadog/ci/contrib/rspec/integration.rbs @@ -2,15 +2,12 @@ module Datadog module CI module Contrib module RSpec - class Integration - extend Datadog::CI::Contrib::Integration::ClassMethods - include Datadog::CI::Contrib::Integration::InstanceMethods - + class Integration < Datadog::CI::Contrib::Integration MINIMUM_VERSION: Gem::Version - def self.version: () -> untyped + def version: () -> untyped - def self.loaded?: () -> untyped + def loaded?: () -> untyped def compatible?: () -> bool diff --git a/sig/datadog/ci/contrib/rspec/patcher.rbs b/sig/datadog/ci/contrib/rspec/patcher.rbs index d3e26b75..3e89d310 100644 --- a/sig/datadog/ci/contrib/rspec/patcher.rbs +++ b/sig/datadog/ci/contrib/rspec/patcher.rbs @@ -5,8 +5,6 @@ module Datadog module Patcher include Datadog::CI::Contrib::Patcher - def self?.target_version: () -> String - def self?.patch: () -> void def self?.ci_queue?: () -> bool diff --git a/sig/datadog/ci/contrib/rspec/runner.rbs b/sig/datadog/ci/contrib/rspec/runner.rbs index b0b9c96a..f4e5729d 100644 --- a/sig/datadog/ci/contrib/rspec/runner.rbs +++ b/sig/datadog/ci/contrib/rspec/runner.rbs @@ -10,6 +10,8 @@ module Datadog private + def datadog_integration: () -> Datadog::CI::Contrib::Integration + def datadog_configuration: () -> untyped def test_visibility_component: () -> Datadog::CI::TestVisibility::Component diff --git a/sig/datadog/ci/contrib/selenium/integration.rbs b/sig/datadog/ci/contrib/selenium/integration.rbs index 15a9b87a..29c3483f 100644 --- a/sig/datadog/ci/contrib/selenium/integration.rbs +++ b/sig/datadog/ci/contrib/selenium/integration.rbs @@ -2,17 +2,14 @@ module Datadog module CI module Contrib module Selenium - class Integration - extend Datadog::CI::Contrib::Integration::ClassMethods - include Datadog::CI::Contrib::Integration::InstanceMethods - + class Integration < Datadog::CI::Contrib::Integration MINIMUM_VERSION: Gem::Version - def self.version: () -> untyped + def version: () -> untyped - def self.loaded?: () -> bool + def loaded?: () -> bool - def self.compatible?: () -> bool + def compatible?: () -> bool def auto_instrument?: () -> true diff --git a/sig/datadog/ci/contrib/selenium/navigation.rbs b/sig/datadog/ci/contrib/selenium/navigation.rbs index a6d03044..5b0607a6 100644 --- a/sig/datadog/ci/contrib/selenium/navigation.rbs +++ b/sig/datadog/ci/contrib/selenium/navigation.rbs @@ -8,6 +8,8 @@ module Datadog module InstanceMethods : ::Selenium::WebDriver::Navigation def to: (String url) -> void + def datadog_integration: () -> Datadog::CI::Contrib::Integration + def datadog_configuration: () -> untyped end end diff --git a/sig/datadog/ci/contrib/selenium/patcher.rbs b/sig/datadog/ci/contrib/selenium/patcher.rbs index 83295b0b..06455eeb 100644 --- a/sig/datadog/ci/contrib/selenium/patcher.rbs +++ b/sig/datadog/ci/contrib/selenium/patcher.rbs @@ -5,8 +5,6 @@ module Datadog module Patcher include Datadog::CI::Contrib::Patcher - def self?.target_version: () -> untyped - def self?.patch: () -> untyped end end diff --git a/sig/datadog/ci/contrib/simplecov/integration.rbs b/sig/datadog/ci/contrib/simplecov/integration.rbs index 679df849..64f238b7 100644 --- a/sig/datadog/ci/contrib/simplecov/integration.rbs +++ b/sig/datadog/ci/contrib/simplecov/integration.rbs @@ -2,17 +2,14 @@ module Datadog module CI module Contrib module Simplecov - class Integration - extend Datadog::CI::Contrib::Integration::ClassMethods - include Datadog::CI::Contrib::Integration::InstanceMethods - + class Integration < Datadog::CI::Contrib::Integration MINIMUM_VERSION: Gem::Version - def self.version: () -> untyped + def version: () -> untyped - def self.loaded?: () -> bool + def loaded?: () -> bool - def self.compatible?: () -> bool + def compatible?: () -> bool def auto_instrument?: () -> bool diff --git a/sig/datadog/ci/contrib/simplecov/patcher.rbs b/sig/datadog/ci/contrib/simplecov/patcher.rbs index 65cb472f..c887a181 100644 --- a/sig/datadog/ci/contrib/simplecov/patcher.rbs +++ b/sig/datadog/ci/contrib/simplecov/patcher.rbs @@ -5,8 +5,6 @@ module Datadog module Patcher include Datadog::CI::Contrib::Patcher - def self?.target_version: () -> untyped - def self?.patch: () -> untyped end end diff --git a/spec/datadog/ci/configuration/settings_spec.rb b/spec/datadog/ci/configuration/settings_spec.rb index 20f83739..77cbfab3 100644 --- a/spec/datadog/ci/configuration/settings_spec.rb +++ b/spec/datadog/ci/configuration/settings_spec.rb @@ -1,45 +1,43 @@ # frozen_string_literal: true # Dummy Integration -class FakeIntegration - include Datadog::CI::Contrib::Integration +module Fake + class Integration < Datadog::CI::Contrib::Integration + module Patcher + module_function - register_as :fake + def patched? + @patched + end - module Patcher - module_function + def patch + @patched = true + end - def patched? - @patched + def reset + @patched = nil + end end - def patch - @patched = true + def version + "0.1" end - def reset - @patched = nil + def loaded? + true end - end - def self.version - "0.1" - end - - def self.loaded? - true - end - - def self.compatible? - true - end + def compatible? + true + end - def self.auto_instrument? - false - end + def auto_instrument? + false + end - def patcher - Patcher + def patcher + Patcher + end end end @@ -580,7 +578,6 @@ def patcher describe "#instrument" do let(:integration_name) { :fake } - let(:integration) { FakeIntegration.new } let(:enabled) { true } subject(:instrument) { settings.ci.instrument(integration_name, enabled: enabled) } @@ -590,7 +587,7 @@ def patcher end after do - FakeIntegration::Patcher.reset + Fake::Integration::Patcher.reset end context "ci enabled" do @@ -598,14 +595,14 @@ def patcher context "when integration exists" do it "patches the integration" do - expect(FakeIntegration::Patcher).to receive(:patch) + expect(Fake::Integration::Patcher).to receive(:patch) instrument end context "when called multiple times" do it "does not patch the integration multiple times" do - expect(FakeIntegration::Patcher).to receive(:patch).and_call_original.once + expect(Fake::Integration::Patcher).to receive(:patch).and_call_original.once instrument instrument @@ -613,30 +610,30 @@ def patcher end context "when not loaded" do - before { allow(FakeIntegration).to receive(:loaded?).and_return(false) } + before { allow_any_instance_of(Fake::Integration).to receive(:loaded?).and_return(false) } it "does not patch the integration" do - expect(FakeIntegration::Patcher).to_not receive(:patch) + expect(Fake::Integration::Patcher).to_not receive(:patch) instrument end end context "when not available" do - before { allow(FakeIntegration).to receive(:available?).and_return(false) } + before { allow_any_instance_of(Fake::Integration).to receive(:available?).and_return(false) } it "does not patch the integration" do - expect(FakeIntegration::Patcher).to_not receive(:patch) + expect(Fake::Integration::Patcher).to_not receive(:patch) instrument end end context "when not compatible" do - before { allow(FakeIntegration).to receive(:compatible?).and_return(false) } + before { allow_any_instance_of(Fake::Integration).to receive(:compatible?).and_return(false) } it "does not patch the integration" do - expect(FakeIntegration::Patcher).to_not receive(:patch) + expect(Fake::Integration::Patcher).to_not receive(:patch) instrument end @@ -646,7 +643,7 @@ def patcher let(:enabled) { false } it "does not patch the integration" do - expect(FakeIntegration::Patcher).to_not receive(:patch) + expect(Fake::Integration::Patcher).to_not receive(:patch) instrument end @@ -665,7 +662,7 @@ def patcher let(:ci_enabled) { false } it "does not patch the integration" do - expect(FakeIntegration::Patcher).to_not receive(:patch) + expect(Fake::Integration::Patcher).to_not receive(:patch) instrument end end diff --git a/spec/datadog/ci/contrib/cucumber/instrumentation_spec.rb b/spec/datadog/ci/contrib/cucumber/instrumentation_spec.rb index e775fd4e..e923435b 100644 --- a/spec/datadog/ci/contrib/cucumber/instrumentation_spec.rb +++ b/spec/datadog/ci/contrib/cucumber/instrumentation_spec.rb @@ -4,6 +4,7 @@ require "securerandom" RSpec.describe "Cucumber instrumentation" do + let(:integration) { Datadog::CI::Contrib::Instrumentation.fetch_integration(:cucumber) } let(:cucumber_features_root) { File.join(__dir__, "features") } let(:enable_retries_failed) { false } let(:single_test_retries_count) { 5 } @@ -34,9 +35,9 @@ let(:bundle_path) { "step_definitions/helpers" } end - let(:cucumber_9_or_above) { Gem::Version.new("9.0.0") <= Datadog::CI::Contrib::Cucumber::Integration.version } - let(:cucumber_8_or_above) { Gem::Version.new("8.0.0") <= Datadog::CI::Contrib::Cucumber::Integration.version } - let(:cucumber_4_or_above) { Gem::Version.new("4.0.0") <= Datadog::CI::Contrib::Cucumber::Integration.version } + let(:cucumber_9_or_above) { Gem::Version.new("9.0.0") <= integration.version } + let(:cucumber_8_or_above) { Gem::Version.new("8.0.0") <= integration.version } + let(:cucumber_4_or_above) { Gem::Version.new("4.0.0") <= integration.version } let(:run_id) { SecureRandom.random_number(2**64 - 1) } let(:steps_file_definition_path) { "spec/datadog/ci/contrib/cucumber/features/step_definitions/steps.rb" } @@ -121,10 +122,7 @@ expect(scenario_span).to have_test_tag(:type, "test") expect(scenario_span).to have_test_tag(:framework, "cucumber") - expect(scenario_span).to have_test_tag( - :framework_version, - Datadog::CI::Contrib::Cucumber::Integration.version.to_s - ) + expect(scenario_span).to have_test_tag(:framework_version, integration.version.to_s) expect(scenario_span).to have_pass_status @@ -174,10 +172,7 @@ expect(test_session_span.service).to eq("jalapenos") expect(test_session_span).to have_test_tag(:span_kind, "test") expect(test_session_span).to have_test_tag(:framework, "cucumber") - expect(test_session_span).to have_test_tag( - :framework_version, - Datadog::CI::Contrib::Cucumber::Integration.version.to_s - ) + expect(test_session_span).to have_test_tag(:framework_version, integration.version.to_s) expect(test_session_span).to have_pass_status # ITR @@ -198,7 +193,7 @@ expect(test_module_span).to have_test_tag(:framework, "cucumber") expect(test_module_span).to have_test_tag( :framework_version, - Datadog::CI::Contrib::Cucumber::Integration.version.to_s + integration.version.to_s ) expect(test_module_span).to have_pass_status end @@ -211,7 +206,7 @@ expect(first_test_suite_span).to have_test_tag(:framework, "cucumber") expect(first_test_suite_span).to have_test_tag( :framework_version, - Datadog::CI::Contrib::Cucumber::Integration.version.to_s + integration.version.to_s ) expect(first_test_suite_span).to have_test_tag( diff --git a/spec/datadog/ci/contrib/cucumber/integration_spec.rb b/spec/datadog/ci/contrib/cucumber/integration_spec.rb index de70ee02..7c2ce6ee 100644 --- a/spec/datadog/ci/contrib/cucumber/integration_spec.rb +++ b/spec/datadog/ci/contrib/cucumber/integration_spec.rb @@ -4,7 +4,7 @@ let(:integration) { described_class.new } describe ".version" do - subject(:version) { described_class.version } + subject(:version) { integration.version } context 'when the "cucumber" gem is loaded' do include_context "loaded gems", "cucumber" => described_class::MINIMUM_VERSION @@ -18,7 +18,7 @@ end describe ".loaded?" do - subject(:loaded?) { described_class.loaded? } + subject(:loaded?) { integration.loaded? } context "when Cucumber::Runtime is defined" do before { stub_const("Cucumber::Runtime", Class.new) } @@ -34,7 +34,7 @@ end describe ".compatible?" do - subject(:compatible?) { described_class.compatible? } + subject(:compatible?) { integration.compatible? } context 'when "cucumber" gem is loaded with a version' do context "that is less than the minimum" do diff --git a/spec/datadog/ci/contrib/knapsack_rspec/instrumentation_spec.rb b/spec/datadog/ci/contrib/knapsack_rspec/instrumentation_spec.rb index 65486f8d..545eb2c5 100644 --- a/spec/datadog/ci/contrib/knapsack_rspec/instrumentation_spec.rb +++ b/spec/datadog/ci/contrib/knapsack_rspec/instrumentation_spec.rb @@ -2,6 +2,8 @@ require "fileutils" RSpec.describe "RSpec instrumentation with Knapsack Pro runner in queue mode" do + let(:integration) { Datadog::CI::Contrib::Instrumentation.fetch_integration(:rspec) } + before do # expect that public manual API isn't used expect(Datadog::CI).to receive(:start_test_session).never @@ -39,6 +41,9 @@ # test session and module traced expect(test_session_span).not_to be_nil + expect(test_session_span).to have_test_tag(:framework, "rspec") + expect(test_session_span).to have_test_tag(:framework_version, integration.version.to_s) + expect(test_module_span).not_to be_nil # test session and module are failed diff --git a/spec/datadog/ci/contrib/knapsack_rspec_go/instrumentation_spec.rb b/spec/datadog/ci/contrib/knapsack_rspec_go/instrumentation_spec.rb index 43606d14..48034723 100644 --- a/spec/datadog/ci/contrib/knapsack_rspec_go/instrumentation_spec.rb +++ b/spec/datadog/ci/contrib/knapsack_rspec_go/instrumentation_spec.rb @@ -2,6 +2,8 @@ require "fileutils" RSpec.describe "Knapsack Pro runner when Datadog::CI is configured during the knapsack run like in rspec_go rake task" do + let(:integration) { Datadog::CI::Contrib::Instrumentation.fetch_integration(:rspec) } + before do # expect that public manual API isn't used expect(Datadog::CI).to receive(:start_test_session).never @@ -47,6 +49,9 @@ # test session and module traced expect(test_session_span).not_to be_nil + expect(test_session_span).to have_test_tag(:framework, "rspec") + expect(test_session_span).to have_test_tag(:framework_version, integration.version.to_s) + expect(test_module_span).not_to be_nil # test session and module are failed diff --git a/spec/datadog/ci/contrib/minitest/instrumentation_spec.rb b/spec/datadog/ci/contrib/minitest/instrumentation_spec.rb index 942089dc..89b45d81 100644 --- a/spec/datadog/ci/contrib/minitest/instrumentation_spec.rb +++ b/spec/datadog/ci/contrib/minitest/instrumentation_spec.rb @@ -10,6 +10,8 @@ module Kernel end RSpec.describe "Minitest instrumentation" do + let(:integration) { Datadog::CI::Contrib::Instrumentation.fetch_integration(:minitest) } + before do # expect that public manual API isn't used expect(Datadog::CI).to receive(:start_test_session).never @@ -77,7 +79,7 @@ def test_foo expect(span).to have_test_tag(:framework, "minitest") expect(span).to have_test_tag( :framework_version, - Datadog::CI::Contrib::Minitest::Integration.version.to_s + integration.version.to_s ) expect(span).to have_pass_status @@ -86,7 +88,7 @@ def test_foo :source_file, "spec/datadog/ci/contrib/minitest/instrumentation_spec.rb" ) - expect(span).to have_test_tag(:source_start, "59") + expect(span).to have_test_tag(:source_start, "61") expect(span).to have_test_tag( :codeowners, "[\"@DataDog/ruby-guild\", \"@DataDog/ci-app-libraries\"]" @@ -437,7 +439,7 @@ def test_pass_other expect(test_session_span).to have_test_tag(:framework, "minitest") expect(test_session_span).to have_test_tag( :framework_version, - Datadog::CI::Contrib::Minitest::Integration.version.to_s + integration.version.to_s ) # ITR @@ -462,7 +464,7 @@ def test_pass_other expect(test_module_span).to have_test_tag(:framework, "minitest") expect(test_module_span).to have_test_tag( :framework_version, - Datadog::CI::Contrib::Minitest::Integration.version.to_s + integration.version.to_s ) expect(test_module_span).to have_pass_status end @@ -477,14 +479,14 @@ def test_pass_other expect(first_test_suite_span).to have_test_tag(:framework, "minitest") expect(first_test_suite_span).to have_test_tag( :framework_version, - Datadog::CI::Contrib::Minitest::Integration.version.to_s + integration.version.to_s ) expect(first_test_suite_span).to have_test_tag( :source_file, "spec/datadog/ci/contrib/minitest/instrumentation_spec.rb" ) - expect(first_test_suite_span).to have_test_tag(:source_start, "415") + expect(first_test_suite_span).to have_test_tag(:source_start, "417") expect(first_test_suite_span).to have_test_tag( :codeowners, "[\"@DataDog/ruby-guild\", \"@DataDog/ci-app-libraries\"]" @@ -507,7 +509,7 @@ def test_pass_other expect(first_test_span).to have_test_tag(:framework, "minitest") expect(first_test_span).to have_test_tag( :framework_version, - Datadog::CI::Contrib::Minitest::Integration.version.to_s + integration.version.to_s ) expect(first_test_span).to have_pass_status diff --git a/spec/datadog/ci/contrib/minitest/integration_spec.rb b/spec/datadog/ci/contrib/minitest/integration_spec.rb index 105f17da..e8c88efa 100644 --- a/spec/datadog/ci/contrib/minitest/integration_spec.rb +++ b/spec/datadog/ci/contrib/minitest/integration_spec.rb @@ -4,7 +4,7 @@ let(:integration) { described_class.new } describe ".version" do - subject(:version) { described_class.version } + subject(:version) { integration.version } context 'when the "minitest" gem is loaded' do include_context "loaded gems", "minitest" => described_class::MINIMUM_VERSION @@ -18,7 +18,7 @@ end describe ".loaded?" do - subject(:loaded?) { described_class.loaded? } + subject(:loaded?) { integration.loaded? } context "when Minitest is defined" do it { is_expected.to be true } @@ -26,7 +26,7 @@ end describe ".compatible?" do - subject(:compatible?) { described_class.compatible? } + subject(:compatible?) { integration.compatible? } context 'when "minitest" gem is loaded with a version' do context "that is less than the minimum" do diff --git a/spec/datadog/ci/contrib/rspec/instrumentation_spec.rb b/spec/datadog/ci/contrib/rspec/instrumentation_spec.rb index 7d71a6b9..22e2c1ae 100644 --- a/spec/datadog/ci/contrib/rspec/instrumentation_spec.rb +++ b/spec/datadog/ci/contrib/rspec/instrumentation_spec.rb @@ -1,6 +1,8 @@ require "time" -RSpec.describe "RSpec hooks" do +RSpec.describe "RSpec instrumentation" do + let(:integration) { Datadog::CI::Contrib::Instrumentation.fetch_integration(:rspec) } + before do # expect that public manual API isn't used expect(Datadog::CI).to receive(:start_test_session).never @@ -139,7 +141,7 @@ def rspec_session_run( expect(first_test_span).to have_test_tag(:framework, "rspec") expect(first_test_span).to have_test_tag( :framework_version, - Datadog::CI::Contrib::RSpec::Integration.version.to_s + integration.version.to_s ) expect(first_test_span).to have_pass_status @@ -148,7 +150,7 @@ def rspec_session_run( :source_file, "spec/datadog/ci/contrib/rspec/instrumentation_spec.rb" ) - expect(first_test_span).to have_test_tag(:source_start, "121") + expect(first_test_span).to have_test_tag(:source_start, "123") expect(first_test_span).to have_test_tag( :codeowners, "[\"@DataDog/ruby-guild\", \"@DataDog/ci-app-libraries\"]" @@ -525,7 +527,7 @@ def expect_failure expect(test_session_span).to have_test_tag(:framework, "rspec") expect(test_session_span).to have_test_tag( :framework_version, - Datadog::CI::Contrib::RSpec::Integration.version.to_s + integration.version.to_s ) expect(test_session_span).not_to have_test_tag(:code_coverage_enabled) @@ -554,7 +556,7 @@ def expect_failure expect(test_module_span).to have_test_tag(:framework, "rspec") expect(test_module_span).to have_test_tag( :framework_version, - Datadog::CI::Contrib::RSpec::Integration.version.to_s + integration.version.to_s ) expect(test_module_span).to have_pass_status end @@ -571,14 +573,14 @@ def expect_failure expect(first_test_suite_span).to have_test_tag(:framework, "rspec") expect(first_test_suite_span).to have_test_tag( :framework_version, - Datadog::CI::Contrib::RSpec::Integration.version.to_s + integration.version.to_s ) expect(first_test_suite_span).to have_test_tag( :source_file, "spec/datadog/ci/contrib/rspec/instrumentation_spec.rb" ) - expect(first_test_suite_span).to have_test_tag(:source_start, "39") + expect(first_test_suite_span).to have_test_tag(:source_start, "41") expect(first_test_suite_span).to have_test_tag( :codeowners, "[\"@DataDog/ruby-guild\", \"@DataDog/ci-app-libraries\"]" diff --git a/spec/datadog/ci/contrib/rspec/integration_spec.rb b/spec/datadog/ci/contrib/rspec/integration_spec.rb index b387a13a..93f6dd5e 100644 --- a/spec/datadog/ci/contrib/rspec/integration_spec.rb +++ b/spec/datadog/ci/contrib/rspec/integration_spec.rb @@ -4,7 +4,7 @@ let(:integration) { described_class.new } describe ".version" do - subject(:version) { described_class.version } + subject(:version) { integration.version } context 'when the "rspec-core" gem is loaded' do include_context "loaded gems", "rspec-core" => described_class::MINIMUM_VERSION @@ -18,7 +18,7 @@ end describe ".loaded?" do - subject(:loaded?) { described_class.loaded? } + subject(:loaded?) { integration.loaded? } context "when RSpec is defined" do it { is_expected.to be true } @@ -26,7 +26,7 @@ end describe ".compatible?" do - subject(:compatible?) { described_class.compatible? } + subject(:compatible?) { integration.compatible? } context 'when "rspec-core" gem is loaded with a version' do context "that is less than the minimum" do From 8e7e0639263ed5021123e5a0da28e143f800eb21 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 14 Nov 2024 12:36:32 +0100 Subject: [PATCH 4/7] fix knapsack integration and remove duplicated knapsack instrumentation specs --- Rakefile | 4 - lib/datadog/ci/contrib/instrumentation.rb | 19 ++++- lib/datadog/ci/contrib/integration.rb | 7 ++ .../ci/contrib/knapsack/integration.rb | 3 +- lib/datadog/ci/contrib/knapsack/runner.rb | 4 +- lib/datadog/ci/contrib/rspec/integration.rb | 4 + .../knapsack_rspec/instrumentation_spec.rb | 20 +++-- .../knapsack_rspec_go/instrumentation_spec.rb | 81 ------------------- .../suite_under_test/some_test_rspec.rb | 13 --- 9 files changed, 43 insertions(+), 112 deletions(-) delete mode 100644 spec/datadog/ci/contrib/knapsack_rspec_go/instrumentation_spec.rb delete mode 100644 spec/datadog/ci/contrib/knapsack_rspec_go/suite_under_test/some_test_rspec.rb diff --git a/Rakefile b/Rakefile index f2633a77..b2106288 100644 --- a/Rakefile +++ b/Rakefile @@ -76,9 +76,6 @@ TEST_METADATA = { "knapsack_rspec" => { "knapsack_pro-7-rspec-3" => "✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ❌ jruby" }, - "knapsack_rspec_go" => { - "knapsack_pro-7-rspec-3" => "✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ❌ jruby" - }, "selenium" => { "selenium-4-capybara-3" => "❌ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ❌ 3.4 / ✅ jruby" }, @@ -156,7 +153,6 @@ namespace :spec do ci_queue_minitest ci_queue_rspec knapsack_rspec - knapsack_rspec_go selenium timecop ].each do |contrib| desc "" # "Explicitly hiding from `rake -T`" diff --git a/lib/datadog/ci/contrib/instrumentation.rb b/lib/datadog/ci/contrib/instrumentation.rb index 21ab384f..0e7c01d0 100644 --- a/lib/datadog/ci/contrib/instrumentation.rb +++ b/lib/datadog/ci/contrib/instrumentation.rb @@ -13,13 +13,24 @@ def self.instrument(integration_name, options = {}, &block) return unless integration.enabled patch_results = integration.patch - return if patch_results == true + if patch_results == true + # try to patch dependant integrations (for example knapsack that depends on rspec) + dependants = integration.dependants + .map { |name| fetch_integration(name) } + .filter { |integration| integration.patchable? } - error_message = <<-ERROR + Datadog.logger.debug("Found dependent integrations for #{integration_name}: #{dependants}") + + dependants.each do |dependent_integration| + dependent_integration.patch + end + else + error_message = <<-ERROR Available?: #{patch_results[:available]}, Loaded?: #{patch_results[:loaded]}, Compatible?: #{patch_results[:compatible]}, Patchable?: #{patch_results[:patchable]}" - ERROR - Datadog.logger.warn("Unable to patch #{integration_name} (#{error_message})") + ERROR + Datadog.logger.warn("Unable to patch #{integration_name} (#{error_message})") + end end def self.fetch_integration(name) diff --git a/lib/datadog/ci/contrib/integration.rb b/lib/datadog/ci/contrib/integration.rb index c8056da5..87fc84a3 100644 --- a/lib/datadog/ci/contrib/integration.rb +++ b/lib/datadog/ci/contrib/integration.rb @@ -24,6 +24,13 @@ def self.registry @registry end + # List of integrations names that depend on this integration. + # Specify when you might need to automatically instrument other integrations (like test runner for the + # test framework). + def dependants + [] + end + # Version of the integration target code in the environment. # # This is the gem version, when the instrumentation target is a Ruby gem. diff --git a/lib/datadog/ci/contrib/knapsack/integration.rb b/lib/datadog/ci/contrib/knapsack/integration.rb index 838a967d..c687955b 100644 --- a/lib/datadog/ci/contrib/knapsack/integration.rb +++ b/lib/datadog/ci/contrib/knapsack/integration.rb @@ -17,7 +17,8 @@ def version end def loaded? - !defined?(::KnapsackPro).nil? && !defined?(::KnapsackPro::Extensions::RSpecExtension).nil? && + !defined?(::KnapsackPro).nil? && + !defined?(::KnapsackPro::Extensions::RSpecExtension).nil? && !defined?(::KnapsackPro::Extensions::RSpecExtension::Runner).nil? end diff --git a/lib/datadog/ci/contrib/knapsack/runner.rb b/lib/datadog/ci/contrib/knapsack/runner.rb index 9e399a22..aae5b604 100644 --- a/lib/datadog/ci/contrib/knapsack/runner.rb +++ b/lib/datadog/ci/contrib/knapsack/runner.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "../../../ext/test" -require_relative "../ext" +require_relative "../../ext/test" +require_relative "../rspec/ext" require_relative "../instrumentation" module Datadog diff --git a/lib/datadog/ci/contrib/rspec/integration.rb b/lib/datadog/ci/contrib/rspec/integration.rb index bfe0b33a..346ae8ed 100644 --- a/lib/datadog/ci/contrib/rspec/integration.rb +++ b/lib/datadog/ci/contrib/rspec/integration.rb @@ -12,6 +12,10 @@ module RSpec class Integration < Contrib::Integration MINIMUM_VERSION = Gem::Version.new("3.0.0") + def dependants + %i[knapsack] + end + def version Gem.loaded_specs["rspec-core"]&.version end diff --git a/spec/datadog/ci/contrib/knapsack_rspec/instrumentation_spec.rb b/spec/datadog/ci/contrib/knapsack_rspec/instrumentation_spec.rb index 545eb2c5..4f56c886 100644 --- a/spec/datadog/ci/contrib/knapsack_rspec/instrumentation_spec.rb +++ b/spec/datadog/ci/contrib/knapsack_rspec/instrumentation_spec.rb @@ -1,7 +1,7 @@ require "knapsack_pro" require "fileutils" -RSpec.describe "RSpec instrumentation with Knapsack Pro runner in queue mode" do +RSpec.describe "Knapsack Pro runner when Datadog::CI is configured during the knapsack run like in rspec_go rake task" do let(:integration) { Datadog::CI::Contrib::Instrumentation.fetch_integration(:rspec) } before do @@ -12,15 +12,20 @@ expect(Datadog::CI).to receive(:start_test).never end - include_context "CI mode activated" do - let(:integration_name) { :rspec } - end + include_context "CI mode activated" before do + allow_any_instance_of(Datadog::Core::Remote::Negotiation).to( + receive(:endpoint?).with("/evp_proxy/v4/").and_return(true) + ) + + allow(Datadog::CI::Utils::TestRun).to receive(:command).and_return("knapsack:queue:rspec") + allow_any_instance_of(KnapsackPro::Runners::Queue::RSpecRunner).to receive(:test_file_paths).and_return( ["./spec/datadog/ci/contrib/knapsack_rspec/suite_under_test/some_test_rspec.rb"], [] ) + # raise to prevent Knapsack from running Kernel.exit(0) allow(KnapsackPro::Report).to receive(:save_node_queue_to_api).and_raise(ArgumentError) end @@ -28,12 +33,13 @@ it "instruments this rspec session" do with_new_rspec_environment do ClimateControl.modify( - "KNAPSACK_PRO_CI_NODE_BUILD_ID" => "142", + "KNAPSACK_PRO_CI_NODE_BUILD_ID" => "144", "KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC" => "example_token", - "KNAPSACK_PRO_FIXED_QUEUE_SPLIT" => "true" + "KNAPSACK_PRO_FIXED_QUEUE_SPLIT" => "true", + "KNAPSACK_PRO_QUEUE_ID" => nil ) do KnapsackPro::Adapters::RSpecAdapter.bind - KnapsackPro::Runners::Queue::RSpecRunner.run("", devnull, devnull) + KnapsackPro::Runners::Queue::RSpecRunner.run("--require knapsack_helper", devnull, devnull) rescue ArgumentError # suppress invalid API key error end diff --git a/spec/datadog/ci/contrib/knapsack_rspec_go/instrumentation_spec.rb b/spec/datadog/ci/contrib/knapsack_rspec_go/instrumentation_spec.rb deleted file mode 100644 index 48034723..00000000 --- a/spec/datadog/ci/contrib/knapsack_rspec_go/instrumentation_spec.rb +++ /dev/null @@ -1,81 +0,0 @@ -require "knapsack_pro" -require "fileutils" - -RSpec.describe "Knapsack Pro runner when Datadog::CI is configured during the knapsack run like in rspec_go rake task" do - let(:integration) { Datadog::CI::Contrib::Instrumentation.fetch_integration(:rspec) } - - before do - # expect that public manual API isn't used - expect(Datadog::CI).to receive(:start_test_session).never - expect(Datadog::CI).to receive(:start_test_module).never - expect(Datadog::CI).to receive(:start_test_suite).never - expect(Datadog::CI).to receive(:start_test).never - end - - include_context "CI mode activated" do - let(:integration_name) { :rspec } - end - - before do - allow_any_instance_of(Datadog::Core::Remote::Negotiation).to( - receive(:endpoint?).with("/evp_proxy/v4/").and_return(true) - ) - - allow(Datadog::CI::Utils::TestRun).to receive(:command).and_return("knapsack:queue:rspec") - - allow_any_instance_of(KnapsackPro::Runners::Queue::RSpecRunner).to receive(:test_file_paths).and_return( - ["./spec/datadog/ci/contrib/knapsack_rspec_go/suite_under_test/some_test_rspec.rb"], - [] - ) - - # raise to prevent Knapsack from running Kernel.exit(0) - allow(KnapsackPro::Report).to receive(:save_node_queue_to_api).and_raise(ArgumentError) - end - - it "instruments this rspec session" do - with_new_rspec_environment do - ClimateControl.modify( - "KNAPSACK_PRO_CI_NODE_BUILD_ID" => "144", - "KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC" => "example_token", - "KNAPSACK_PRO_FIXED_QUEUE_SPLIT" => "true", - "KNAPSACK_PRO_QUEUE_ID" => nil - ) do - KnapsackPro::Adapters::RSpecAdapter.bind - KnapsackPro::Runners::Queue::RSpecRunner.run("--require knapsack_helper", devnull, devnull) - rescue ArgumentError - # suppress invalid API key error - end - end - - # test session and module traced - expect(test_session_span).not_to be_nil - expect(test_session_span).to have_test_tag(:framework, "rspec") - expect(test_session_span).to have_test_tag(:framework_version, integration.version.to_s) - - expect(test_module_span).not_to be_nil - - # test session and module are failed - expect([test_session_span, test_module_span]).to all have_fail_status - - # single test suite span - expect(test_suite_spans).to have(1).item - expect(test_suite_spans.first).to have_test_tag(:status, Datadog::CI::Ext::Test::Status::FAIL) - expect(test_suite_spans.first).to have_test_tag( - :suite, - "SomeTest at ./spec/datadog/ci/contrib/knapsack_rspec_go/suite_under_test/some_test_rspec.rb" - ) - - # there is test span for every test case - expect(test_spans).to have(2).items - # test spans belong to a single test suite - expect(test_spans).to have_unique_tag_values_count(:test_suite_id, 1) - expect(test_spans).to have_tag_values_no_order( - :status, - [Datadog::CI::Ext::Test::Status::FAIL, Datadog::CI::Ext::Test::Status::PASS] - ) - - # every test span is connected to test module and test session - expect(test_spans).to all have_test_tag(:test_module_id) - expect(test_spans).to all have_test_tag(:test_session_id) - end -end diff --git a/spec/datadog/ci/contrib/knapsack_rspec_go/suite_under_test/some_test_rspec.rb b/spec/datadog/ci/contrib/knapsack_rspec_go/suite_under_test/some_test_rspec.rb deleted file mode 100644 index 7b775cb9..00000000 --- a/spec/datadog/ci/contrib/knapsack_rspec_go/suite_under_test/some_test_rspec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require "rspec" - -RSpec.describe "SomeTest" do - context "nested" do - it "foo" do - # DO NOTHING - end - - it "fails" do - expect(1).to eq(2) - end - end -end From 489dd2825540e60e441403c9a4841f5d5e73df1f Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 15 Nov 2024 08:14:26 +0100 Subject: [PATCH 5/7] move integrations registry from Integration class to Instrumentation module --- lib/datadog/ci/contrib/contrib.rb | 4 ++-- lib/datadog/ci/contrib/instrumentation.rb | 20 +++++++++++++++++++- lib/datadog/ci/contrib/integration.rb | 17 ++--------------- sig/datadog/ci/contrib/instrumentation.rbs | 8 ++++++++ sig/datadog/ci/contrib/integration.rbs | 6 ------ 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/lib/datadog/ci/contrib/contrib.rb b/lib/datadog/ci/contrib/contrib.rb index 457a7e74..69a6a19f 100644 --- a/lib/datadog/ci/contrib/contrib.rb +++ b/lib/datadog/ci/contrib/contrib.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "integration" +require_relative "instrumentation" module Datadog module CI @@ -13,7 +13,7 @@ module Contrib def self.auto_instrument_on_session_start! Datadog.logger.debug("Auto instrumenting all integrations...") - Integration.registry.each do |name, integration| + Instrumentation.registry.each do |name, integration| next unless integration.auto_instrument? Datadog.logger.debug "#{name} is allowed to be auto instrumented" diff --git a/lib/datadog/ci/contrib/instrumentation.rb b/lib/datadog/ci/contrib/instrumentation.rb index 0e7c01d0..bf2042e9 100644 --- a/lib/datadog/ci/contrib/instrumentation.rb +++ b/lib/datadog/ci/contrib/instrumentation.rb @@ -6,6 +6,16 @@ module Contrib module Instrumentation class InvalidIntegrationError < StandardError; end + @registry = {} + + def self.registry + @registry + end + + def self.register_integration(integration_class) + @registry[integration_name(integration_class)] = integration_class.new + end + def self.instrument(integration_name, options = {}, &block) integration = fetch_integration(integration_name) integration.configure(options, &block) @@ -34,9 +44,17 @@ def self.instrument(integration_name, options = {}, &block) end def self.fetch_integration(name) - Datadog::CI::Contrib::Integration.registry[name] || + @registry[name] || raise(InvalidIntegrationError, "'#{name}' is not a valid integration.") end + + # take the parent module name and downcase it + # for example for Datadog::CI::Contrib::RSpec::Integration it will be :rspec + def self.integration_name(subclass) + result = subclass.name&.split("::")&.[](-2)&.downcase&.to_sym + raise "Integration name could not be derived for #{subclass}" if result.nil? + result + end end end end diff --git a/lib/datadog/ci/contrib/integration.rb b/lib/datadog/ci/contrib/integration.rb index 87fc84a3..1c6cda21 100644 --- a/lib/datadog/ci/contrib/integration.rb +++ b/lib/datadog/ci/contrib/integration.rb @@ -1,27 +1,14 @@ # frozen_string_literal: true require_relative "settings" +require_relative "instrumentation" module Datadog module CI module Contrib class Integration - @registry = {} - def self.inherited(subclass) - @registry[integration_name(subclass)] = subclass.new - end - - # take the parent module name and downcase it - # for example for Datadog::CI::Contrib::RSpec::Integration it will be :rspec - def self.integration_name(subclass) - result = subclass.name&.split("::")&.[](-2)&.downcase&.to_sym - raise "Integration name could not be derived for #{subclass}" if result.nil? - result - end - - def self.registry - @registry + Instrumentation.register_integration(subclass) end # List of integrations names that depend on this integration. diff --git a/sig/datadog/ci/contrib/instrumentation.rbs b/sig/datadog/ci/contrib/instrumentation.rbs index 958c9862..e43cdaf6 100644 --- a/sig/datadog/ci/contrib/instrumentation.rbs +++ b/sig/datadog/ci/contrib/instrumentation.rbs @@ -5,9 +5,17 @@ module Datadog class InvalidIntegrationError < StandardError end + self.@registry: Hash[Symbol, untyped] + + def self.registry: () -> Hash[Symbol, untyped] + def self.instrument: (Symbol integration_name, ?::Hash[untyped, untyped] options) { (?) -> untyped } -> void def self.fetch_integration: (Symbol name) -> untyped + + def self.integration_name: (Class) -> Symbol + + def self.register_integration: (Class integration) -> void end end end diff --git a/sig/datadog/ci/contrib/integration.rbs b/sig/datadog/ci/contrib/integration.rbs index 4708e3a2..11659090 100644 --- a/sig/datadog/ci/contrib/integration.rbs +++ b/sig/datadog/ci/contrib/integration.rbs @@ -2,14 +2,8 @@ module Datadog module CI module Contrib class Integration - self.@registry: Hash[Symbol, untyped] - def self.inherited: (untyped) -> void - def self.registry: () -> Hash[Symbol, untyped] - - def self.integration_name: (Class) -> Symbol - def version: () -> untyped def available?: () -> bool From a879e6ab779d7cfec9a36cab64a06c87d08a78c3 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 15 Nov 2024 08:54:04 +0100 Subject: [PATCH 6/7] consolidate all instrumentation code in the Instrumentation module, rename auto_instrument method to late_instrument --- lib/datadog/ci/contrib/contrib.rb | 31 ------------------- .../ci/contrib/cucumber/integration.rb | 5 --- lib/datadog/ci/contrib/instrumentation.rb | 27 ++++++++++++++++ lib/datadog/ci/contrib/integration.rb | 4 +-- .../ci/contrib/knapsack/integration.rb | 5 --- .../ci/contrib/minitest/integration.rb | 5 --- lib/datadog/ci/contrib/rspec/integration.rb | 5 --- .../ci/contrib/selenium/integration.rb | 4 +-- .../ci/contrib/simplecov/integration.rb | 4 +-- lib/datadog/ci/test_visibility/component.rb | 4 +-- sig/datadog/ci/contrib/contrib.rbs | 7 ----- .../ci/contrib/cucumber/integration.rbs | 2 +- sig/datadog/ci/contrib/instrumentation.rbs | 2 ++ sig/datadog/ci/contrib/integration.rbs | 2 +- .../ci/contrib/knapsack/integration.rbs | 2 +- .../ci/contrib/minitest/integration.rbs | 2 +- sig/datadog/ci/contrib/rspec/integration.rbs | 2 +- .../ci/contrib/selenium/integration.rbs | 2 +- .../ci/contrib/simplecov/integration.rbs | 2 +- .../datadog/ci/configuration/settings_spec.rb | 2 +- .../ci/contrib/cucumber/integration_spec.rb | 4 +-- .../ci/contrib/minitest/integration_spec.rb | 4 +-- .../ci/contrib/rspec/integration_spec.rb | 4 +-- 23 files changed, 51 insertions(+), 80 deletions(-) delete mode 100644 lib/datadog/ci/contrib/contrib.rb delete mode 100644 sig/datadog/ci/contrib/contrib.rbs diff --git a/lib/datadog/ci/contrib/contrib.rb b/lib/datadog/ci/contrib/contrib.rb deleted file mode 100644 index 69a6a19f..00000000 --- a/lib/datadog/ci/contrib/contrib.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -require_relative "instrumentation" - -module Datadog - module CI - module Contrib - # This method auto instruments all test libraries (ex: selenium-webdriver). - # It is intended to be called when test session starts to add additional capabilities to test visibility. - # - # This method does not automatically instrument test frameworks (ex: RSpec, Cucumber, etc), it requires - # test framework to be already instrumented. - def self.auto_instrument_on_session_start! - Datadog.logger.debug("Auto instrumenting all integrations...") - - Instrumentation.registry.each do |name, integration| - next unless integration.auto_instrument? - - Datadog.logger.debug "#{name} is allowed to be auto instrumented" - - patch_results = integration.patch - if patch_results == true - Datadog.logger.debug("#{name} is patched") - else - Datadog.logger.debug("#{name} is not patched (#{patch_results})") - end - end - end - end - end -end diff --git a/lib/datadog/ci/contrib/cucumber/integration.rb b/lib/datadog/ci/contrib/cucumber/integration.rb index 607c5530..02d9b182 100644 --- a/lib/datadog/ci/contrib/cucumber/integration.rb +++ b/lib/datadog/ci/contrib/cucumber/integration.rb @@ -24,11 +24,6 @@ def compatible? super && version >= MINIMUM_VERSION end - # test environments should not auto instrument test libraries - def auto_instrument? - false - end - def new_configuration Configuration::Settings.new end diff --git a/lib/datadog/ci/contrib/instrumentation.rb b/lib/datadog/ci/contrib/instrumentation.rb index bf2042e9..773ffa5b 100644 --- a/lib/datadog/ci/contrib/instrumentation.rb +++ b/lib/datadog/ci/contrib/instrumentation.rb @@ -16,6 +16,9 @@ def self.register_integration(integration_class) @registry[integration_name(integration_class)] = integration_class.new end + # Manual instrumentation of a specific integration. + # + # This method is called when user has `c.ci.instrument :integration_name` in their code. def self.instrument(integration_name, options = {}, &block) integration = fetch_integration(integration_name) integration.configure(options, &block) @@ -43,6 +46,30 @@ def self.instrument(integration_name, options = {}, &block) end end + # This method instruments all additional test libraries (ex: selenium-webdriver) that need to be instrumented + # later in the test suite run. + # + # It is intended to be called when test session starts to add additional capabilities to test visibility. + # + # This method does not automatically instrument test frameworks (ex: RSpec, Cucumber, etc), it requires + # test framework to be already instrumented. + def self.instrument_on_session_start + Datadog.logger.debug("Instrumenting all late instrumented integrations...") + + @registry.each do |name, integration| + next unless integration.late_instrument? + + Datadog.logger.debug "#{name} is allowed to be late instrumented" + + patch_results = integration.patch + if patch_results == true + Datadog.logger.debug("#{name} is patched") + else + Datadog.logger.debug("#{name} is not patched (#{patch_results})") + end + end + end + def self.fetch_integration(name) @registry[name] || raise(InvalidIntegrationError, "'#{name}' is not a valid integration.") diff --git a/lib/datadog/ci/contrib/integration.rb b/lib/datadog/ci/contrib/integration.rb index 1c6cda21..c33a1a4e 100644 --- a/lib/datadog/ci/contrib/integration.rb +++ b/lib/datadog/ci/contrib/integration.rb @@ -110,8 +110,8 @@ def patch # Can the patch for this integration be applied automatically? # @return [Boolean] can the tracer activate this instrumentation without explicit user input? - def auto_instrument? - true + def late_instrument? + false end # Returns a new configuration object for this integration. diff --git a/lib/datadog/ci/contrib/knapsack/integration.rb b/lib/datadog/ci/contrib/knapsack/integration.rb index c687955b..5836a2db 100644 --- a/lib/datadog/ci/contrib/knapsack/integration.rb +++ b/lib/datadog/ci/contrib/knapsack/integration.rb @@ -26,11 +26,6 @@ def compatible? super && version >= MINIMUM_VERSION end - # test environments should not auto instrument test libraries - def auto_instrument? - false - end - def patcher Patcher end diff --git a/lib/datadog/ci/contrib/minitest/integration.rb b/lib/datadog/ci/contrib/minitest/integration.rb index 9c98b8a7..9a9f84ed 100644 --- a/lib/datadog/ci/contrib/minitest/integration.rb +++ b/lib/datadog/ci/contrib/minitest/integration.rb @@ -24,11 +24,6 @@ def compatible? super && version >= MINIMUM_VERSION end - # test environments should not auto instrument test libraries - def auto_instrument? - false - end - def new_configuration Configuration::Settings.new end diff --git a/lib/datadog/ci/contrib/rspec/integration.rb b/lib/datadog/ci/contrib/rspec/integration.rb index 346ae8ed..6403e5c4 100644 --- a/lib/datadog/ci/contrib/rspec/integration.rb +++ b/lib/datadog/ci/contrib/rspec/integration.rb @@ -29,11 +29,6 @@ def compatible? super && version >= MINIMUM_VERSION end - # test environments should not auto instrument test libraries - def auto_instrument? - false - end - def new_configuration Configuration::Settings.new end diff --git a/lib/datadog/ci/contrib/selenium/integration.rb b/lib/datadog/ci/contrib/selenium/integration.rb index 73f35c66..6dcb3ba8 100644 --- a/lib/datadog/ci/contrib/selenium/integration.rb +++ b/lib/datadog/ci/contrib/selenium/integration.rb @@ -25,8 +25,8 @@ def compatible? super && version >= MINIMUM_VERSION end - # additional instrumentations for test helpers are auto instrumented on test session start - def auto_instrument? + # additional instrumentations for test libraries are late instrumented on test session start + def late_instrument? true end diff --git a/lib/datadog/ci/contrib/simplecov/integration.rb b/lib/datadog/ci/contrib/simplecov/integration.rb index 30c5f6a7..7381c625 100644 --- a/lib/datadog/ci/contrib/simplecov/integration.rb +++ b/lib/datadog/ci/contrib/simplecov/integration.rb @@ -24,8 +24,8 @@ def compatible? super && version >= MINIMUM_VERSION end - # additional instrumentations for test helpers are auto instrumented on test session start - def auto_instrument? + # additional instrumentations for test libraries are late instrumented on test session start + def late_instrument? true end diff --git a/lib/datadog/ci/test_visibility/component.rb b/lib/datadog/ci/test_visibility/component.rb index 2c6b9332..588041dd 100644 --- a/lib/datadog/ci/test_visibility/component.rb +++ b/lib/datadog/ci/test_visibility/component.rb @@ -7,7 +7,7 @@ require_relative "total_coverage" require_relative "../codeowners/parser" -require_relative "../contrib/contrib" +require_relative "../contrib/instrumentation" require_relative "../ext/test" require_relative "../git/local_repository" @@ -149,7 +149,7 @@ def on_test_session_started(test_session) git_tree_upload_worker.perform(test_session.git_repository_url) # finds and instruments additional test libraries that we support (ex: selenium-webdriver) - Contrib.auto_instrument_on_session_start! + Contrib::Instrumentation.instrument_on_session_start # sends internal telemetry events Telemetry.test_session_started(test_session) diff --git a/sig/datadog/ci/contrib/contrib.rbs b/sig/datadog/ci/contrib/contrib.rbs deleted file mode 100644 index 9c44f9a9..00000000 --- a/sig/datadog/ci/contrib/contrib.rbs +++ /dev/null @@ -1,7 +0,0 @@ -module Datadog - module CI - module Contrib - def self.auto_instrument_on_session_start!: () -> void - end - end -end diff --git a/sig/datadog/ci/contrib/cucumber/integration.rbs b/sig/datadog/ci/contrib/cucumber/integration.rbs index 2c589b1d..ec705c7c 100644 --- a/sig/datadog/ci/contrib/cucumber/integration.rbs +++ b/sig/datadog/ci/contrib/cucumber/integration.rbs @@ -11,7 +11,7 @@ module Datadog def compatible?: () -> bool - def auto_instrument?: () -> false + def late_instrument?: () -> false def new_configuration: () -> untyped diff --git a/sig/datadog/ci/contrib/instrumentation.rbs b/sig/datadog/ci/contrib/instrumentation.rbs index e43cdaf6..a10222fd 100644 --- a/sig/datadog/ci/contrib/instrumentation.rbs +++ b/sig/datadog/ci/contrib/instrumentation.rbs @@ -16,6 +16,8 @@ module Datadog def self.integration_name: (Class) -> Symbol def self.register_integration: (Class integration) -> void + + def self.instrument_on_session_start: () -> void end end end diff --git a/sig/datadog/ci/contrib/integration.rbs b/sig/datadog/ci/contrib/integration.rbs index 11659090..e75fe2b6 100644 --- a/sig/datadog/ci/contrib/integration.rbs +++ b/sig/datadog/ci/contrib/integration.rbs @@ -24,7 +24,7 @@ module Datadog def patch: () -> (bool | Hash[Symbol, bool]) - def auto_instrument?: () -> bool + def late_instrument?: () -> bool def new_configuration: () -> Datadog::CI::Contrib::Settings end diff --git a/sig/datadog/ci/contrib/knapsack/integration.rbs b/sig/datadog/ci/contrib/knapsack/integration.rbs index eca02a92..f14b97af 100644 --- a/sig/datadog/ci/contrib/knapsack/integration.rbs +++ b/sig/datadog/ci/contrib/knapsack/integration.rbs @@ -11,7 +11,7 @@ module Datadog def compatible?: () -> bool - def auto_instrument?: () -> bool + def late_instrument?: () -> bool def new_configuration: () -> Datadog::CI::Contrib::Settings diff --git a/sig/datadog/ci/contrib/minitest/integration.rbs b/sig/datadog/ci/contrib/minitest/integration.rbs index 1c10efd1..0c006af7 100644 --- a/sig/datadog/ci/contrib/minitest/integration.rbs +++ b/sig/datadog/ci/contrib/minitest/integration.rbs @@ -11,7 +11,7 @@ module Datadog def compatible?: () -> bool - def auto_instrument?: () -> false + def late_instrument?: () -> false def new_configuration: () -> untyped diff --git a/sig/datadog/ci/contrib/rspec/integration.rbs b/sig/datadog/ci/contrib/rspec/integration.rbs index 04706988..636ffd76 100644 --- a/sig/datadog/ci/contrib/rspec/integration.rbs +++ b/sig/datadog/ci/contrib/rspec/integration.rbs @@ -11,7 +11,7 @@ module Datadog def compatible?: () -> bool - def auto_instrument?: () -> false + def late_instrument?: () -> false def new_configuration: () -> untyped diff --git a/sig/datadog/ci/contrib/selenium/integration.rbs b/sig/datadog/ci/contrib/selenium/integration.rbs index 29c3483f..38ca7c28 100644 --- a/sig/datadog/ci/contrib/selenium/integration.rbs +++ b/sig/datadog/ci/contrib/selenium/integration.rbs @@ -11,7 +11,7 @@ module Datadog def compatible?: () -> bool - def auto_instrument?: () -> true + def late_instrument?: () -> true def new_configuration: () -> untyped diff --git a/sig/datadog/ci/contrib/simplecov/integration.rbs b/sig/datadog/ci/contrib/simplecov/integration.rbs index 64f238b7..76976377 100644 --- a/sig/datadog/ci/contrib/simplecov/integration.rbs +++ b/sig/datadog/ci/contrib/simplecov/integration.rbs @@ -11,7 +11,7 @@ module Datadog def compatible?: () -> bool - def auto_instrument?: () -> bool + def late_instrument?: () -> bool def new_configuration: () -> untyped diff --git a/spec/datadog/ci/configuration/settings_spec.rb b/spec/datadog/ci/configuration/settings_spec.rb index 77cbfab3..1d7951a3 100644 --- a/spec/datadog/ci/configuration/settings_spec.rb +++ b/spec/datadog/ci/configuration/settings_spec.rb @@ -31,7 +31,7 @@ def compatible? true end - def auto_instrument? + def late_instrument? false end diff --git a/spec/datadog/ci/contrib/cucumber/integration_spec.rb b/spec/datadog/ci/contrib/cucumber/integration_spec.rb index 7c2ce6ee..267fe0fd 100644 --- a/spec/datadog/ci/contrib/cucumber/integration_spec.rb +++ b/spec/datadog/ci/contrib/cucumber/integration_spec.rb @@ -54,8 +54,8 @@ end end - describe "#auto_instrument?" do - subject(:auto_instrument?) { integration.auto_instrument? } + describe "#late_instrument?" do + subject(:late_instrument?) { integration.late_instrument? } it { is_expected.to be(false) } end diff --git a/spec/datadog/ci/contrib/minitest/integration_spec.rb b/spec/datadog/ci/contrib/minitest/integration_spec.rb index e8c88efa..d7655f16 100644 --- a/spec/datadog/ci/contrib/minitest/integration_spec.rb +++ b/spec/datadog/ci/contrib/minitest/integration_spec.rb @@ -46,8 +46,8 @@ end end - describe "#auto_instrument?" do - subject(:auto_instrument?) { integration.auto_instrument? } + describe "#late_instrument?" do + subject(:late_instrument?) { integration.late_instrument? } it { is_expected.to be(false) } end diff --git a/spec/datadog/ci/contrib/rspec/integration_spec.rb b/spec/datadog/ci/contrib/rspec/integration_spec.rb index 93f6dd5e..a2f482d6 100644 --- a/spec/datadog/ci/contrib/rspec/integration_spec.rb +++ b/spec/datadog/ci/contrib/rspec/integration_spec.rb @@ -46,8 +46,8 @@ end end - describe "#auto_instrument?" do - subject(:auto_instrument?) { integration.auto_instrument? } + describe "#late_instrument?" do + subject(:late_instrument?) { integration.late_instrument? } it { is_expected.to be(false) } end From b17763c850fedefe9d29eb409590784a984875f0 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 15 Nov 2024 08:59:58 +0100 Subject: [PATCH 7/7] Integration#patch method now always returns Hash --- lib/datadog/ci/contrib/instrumentation.rb | 4 ++-- lib/datadog/ci/contrib/integration.rb | 3 ++- sig/datadog/ci/contrib/integration.rbs | 2 +- sig/datadog/ci/contrib/patcher.rbs | 12 +++++------- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/datadog/ci/contrib/instrumentation.rb b/lib/datadog/ci/contrib/instrumentation.rb index 773ffa5b..a283fe6c 100644 --- a/lib/datadog/ci/contrib/instrumentation.rb +++ b/lib/datadog/ci/contrib/instrumentation.rb @@ -26,7 +26,7 @@ def self.instrument(integration_name, options = {}, &block) return unless integration.enabled patch_results = integration.patch - if patch_results == true + if patch_results[:ok] # try to patch dependant integrations (for example knapsack that depends on rspec) dependants = integration.dependants .map { |name| fetch_integration(name) } @@ -62,7 +62,7 @@ def self.instrument_on_session_start Datadog.logger.debug "#{name} is allowed to be late instrumented" patch_results = integration.patch - if patch_results == true + if patch_results[:ok] Datadog.logger.debug("#{name} is patched") else Datadog.logger.debug("#{name} is not patched (#{patch_results})") diff --git a/lib/datadog/ci/contrib/integration.rb b/lib/datadog/ci/contrib/integration.rb index c33a1a4e..8bb09dd5 100644 --- a/lib/datadog/ci/contrib/integration.rb +++ b/lib/datadog/ci/contrib/integration.rb @@ -97,6 +97,7 @@ def patch patcher_klass = patcher if !patchable? || patcher_klass.nil? return { + ok: false, available: available?, loaded: loaded?, compatible: compatible?, @@ -105,7 +106,7 @@ def patch end patcher_klass.patch - true + {ok: true} end # Can the patch for this integration be applied automatically? diff --git a/sig/datadog/ci/contrib/integration.rbs b/sig/datadog/ci/contrib/integration.rbs index e75fe2b6..96be033b 100644 --- a/sig/datadog/ci/contrib/integration.rbs +++ b/sig/datadog/ci/contrib/integration.rbs @@ -22,7 +22,7 @@ module Datadog def patcher: () -> Datadog::Tracing::Contrib::Patcher? - def patch: () -> (bool | Hash[Symbol, bool]) + def patch: () -> Hash[Symbol, bool] def late_instrument?: () -> bool diff --git a/sig/datadog/ci/contrib/patcher.rbs b/sig/datadog/ci/contrib/patcher.rbs index f63fd48c..1a37bcf0 100644 --- a/sig/datadog/ci/contrib/patcher.rbs +++ b/sig/datadog/ci/contrib/patcher.rbs @@ -2,12 +2,12 @@ module Datadog module CI module Contrib module Patcher - def self.included: (untyped base) -> untyped + def self.included: (Class base) -> void module CommonMethods - attr_accessor patch_error_result: untyped + attr_accessor patch_error_result: Hash[Symbol, untyped] - attr_accessor patch_successful: untyped + attr_accessor patch_successful: bool def patch_name: () -> String? @@ -17,13 +17,11 @@ module Datadog def patch: () -> void - def on_patch_error: (untyped e) -> untyped - - def default_tags: () -> untyped + def on_patch_error: (StandardError e) -> Hash[Symbol, untyped] private - def patch_only_once: () -> untyped + def patch_only_once: () -> Datadog::Core::Utils::OnlyOnce @patch_only_once: Datadog::Core::Utils::OnlyOnce end