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