datadog-ci 1.8.0 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -2
  3. data/README.md +6 -8
  4. data/exe/ddcirb +3 -1
  5. data/lib/datadog/ci/auto_instrument.rb +8 -0
  6. data/lib/datadog/ci/cli/cli.rb +5 -1
  7. data/lib/datadog/ci/cli/command/base.rb +1 -0
  8. data/lib/datadog/ci/cli/command/exec.rb +29 -0
  9. data/lib/datadog/ci/cli/command/skippable_tests_percentage.rb +0 -1
  10. data/lib/datadog/ci/configuration/settings.rb +3 -21
  11. data/lib/datadog/ci/contrib/ciqueue/integration.rb +34 -0
  12. data/lib/datadog/ci/contrib/ciqueue/patcher.rb +23 -0
  13. data/lib/datadog/ci/contrib/cucumber/formatter.rb +10 -5
  14. data/lib/datadog/ci/contrib/cucumber/integration.rb +5 -14
  15. data/lib/datadog/ci/contrib/cucumber/patcher.rb +2 -6
  16. data/lib/datadog/ci/contrib/instrumentation.rb +173 -0
  17. data/lib/datadog/ci/contrib/integration.rb +101 -117
  18. data/lib/datadog/ci/contrib/knapsack/extension.rb +27 -0
  19. data/lib/datadog/ci/contrib/knapsack/integration.rb +36 -0
  20. data/lib/datadog/ci/contrib/knapsack/patcher.rb +29 -0
  21. data/lib/datadog/ci/contrib/knapsack/runner.rb +66 -0
  22. data/lib/datadog/ci/contrib/minitest/integration.rb +6 -14
  23. data/lib/datadog/ci/contrib/minitest/patcher.rb +1 -5
  24. data/lib/datadog/ci/contrib/minitest/runner.rb +6 -1
  25. data/lib/datadog/ci/contrib/minitest/test.rb +6 -1
  26. data/lib/datadog/ci/contrib/patcher.rb +62 -0
  27. data/lib/datadog/ci/contrib/rspec/example.rb +6 -1
  28. data/lib/datadog/ci/contrib/rspec/integration.rb +10 -13
  29. data/lib/datadog/ci/contrib/rspec/patcher.rb +2 -33
  30. data/lib/datadog/ci/contrib/rspec/runner.rb +6 -1
  31. data/lib/datadog/ci/contrib/selenium/capybara_driver.rb +1 -1
  32. data/lib/datadog/ci/contrib/selenium/driver.rb +1 -1
  33. data/lib/datadog/ci/contrib/selenium/integration.rb +6 -10
  34. data/lib/datadog/ci/contrib/selenium/navigation.rb +6 -2
  35. data/lib/datadog/ci/contrib/selenium/patcher.rb +2 -6
  36. data/lib/datadog/ci/contrib/selenium/rum.rb +0 -2
  37. data/lib/datadog/ci/contrib/simplecov/integration.rb +6 -10
  38. data/lib/datadog/ci/contrib/simplecov/patcher.rb +2 -6
  39. data/lib/datadog/ci/test_retries/strategy/retry_new.rb +1 -1
  40. data/lib/datadog/ci/test_visibility/component.rb +2 -2
  41. data/lib/datadog/ci/test_visibility/telemetry.rb +2 -1
  42. data/lib/datadog/ci/version.rb +1 -1
  43. data/lib/datadog/ci.rb +9 -1
  44. metadata +13 -7
  45. data/lib/datadog/ci/contrib/contrib.rb +0 -31
  46. data/lib/datadog/ci/contrib/rspec/knapsack_pro/extension.rb +0 -29
  47. data/lib/datadog/ci/contrib/rspec/knapsack_pro/patcher.rb +0 -26
  48. data/lib/datadog/ci/contrib/rspec/knapsack_pro/runner.rb +0 -62
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd2dfb59d1dd3d17b2e0ccea1f03308fe38b65ce6daeb3f903d158fc0b4ed47b
4
- data.tar.gz: cc1a87dcd6fdab402556df3dd0f8a042a4dc96438c4aa7796091ff71a49435b0
3
+ metadata.gz: 7641cbd9b01a787badb9bab7d3aa74933c99b750b46609c9a625ed3f7daaa11c
4
+ data.tar.gz: 5489c728c4b6c91ff68b515796533801b02dfb623f506da23849e60e343063a3
5
5
  SHA512:
6
- metadata.gz: d7eacb805c74e1c627c3f0494fab65c45b5bf940cbd124b9c027edd0eb4c21fcaaadd052aa5e7a1e60f2bc9d38b6e6787676a9f64e2fe93e234af4e8d3370117
7
- data.tar.gz: 4d10fc9811a302f609fa36be74b0cbd81d4ae690cdeeb1f333025b7ffbdd398ef92bc6eb8ee455f2d34a9bc83ff76b06cbe091c3d4fe697ded4ce8f9ebf5f75d
6
+ metadata.gz: 3d2f41e5eb8f58c3eecb72200658add1799109c7778fad032c6d58ec2fd462a37df7e1a47edfb6cf7390e037c4e30ada85dc95c71983c30629de38b2bf62fd35
7
+ data.tar.gz: 408151cc04150bcc8ec2a0933c0f6d38cd1362b5842e8b145960ba276431681893988cfd0c49ce47f519ae9e7451549b45503a6ea79f123cfd4cc3750cb2a8ea
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.0] - 2024-11-26
4
+
5
+ ### Added
6
+
7
+ * Auto instrumentation ([#259][])
8
+
9
+ ## [1.8.1] - 2024-10-18
10
+
11
+
12
+ ### Fixed
13
+ * Make --spec-path option available to skipped-tests-estimate cli command ([#250][])
14
+
3
15
  ## [1.8.0] - 2024-10-17
4
16
 
5
17
  ### Added
@@ -350,7 +362,9 @@ Currently test suite level visibility is not used by our instrumentation: it wil
350
362
 
351
363
  - Ruby versions < 2.7 no longer supported ([#8][])
352
364
 
353
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.8.0...main
365
+ [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.9.0...main
366
+ [1.9.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.8.1...v1.9.0
367
+ [1.8.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.8.0...v1.8.1
354
368
  [1.8.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.7.0...v1.8.0
355
369
  [1.7.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.6.0...v1.7.0
356
370
  [1.6.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.5.0...v1.6.0
@@ -505,4 +519,6 @@ Currently test suite level visibility is not used by our instrumentation: it wil
505
519
  [#242]: https://github.com/DataDog/datadog-ci-rb/issues/242
506
520
  [#243]: https://github.com/DataDog/datadog-ci-rb/issues/243
507
521
  [#244]: https://github.com/DataDog/datadog-ci-rb/issues/244
508
- [#248]: https://github.com/DataDog/datadog-ci-rb/issues/248
522
+ [#248]: https://github.com/DataDog/datadog-ci-rb/issues/248
523
+ [#250]: https://github.com/DataDog/datadog-ci-rb/issues/250
524
+ [#259]: https://github.com/DataDog/datadog-ci-rb/issues/259
data/README.md CHANGED
@@ -1,9 +1,7 @@
1
- # Datadog Test Visibility for Ruby
1
+ # Datadog Test Optimization for Ruby
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/datadog-ci.svg)](https://badge.fury.io/rb/datadog-ci)
4
4
  [![YARD documentation](https://img.shields.io/badge/YARD-documentation-blue)](https://datadoghq.dev/datadog-ci-rb/)
5
- [![codecov](https://codecov.io/gh/DataDog/datadog-ci-rb/branch/main/graph/badge.svg)](https://app.codecov.io/gh/DataDog/datadog-ci-rb/branch/main)
6
- [![CircleCI](https://dl.circleci.com/status-badge/img/gh/DataDog/datadog-ci-rb/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/DataDog/datadog-ci-rb/tree/main)
7
5
 
8
6
  Datadog's Ruby Library for instrumenting your tests.
9
7
  Learn more on our [official website](https://docs.datadoghq.com/tests/) and check out our [documentation for this library](https://docs.datadoghq.com/tests/setup/ruby/?tab=cloudciprovideragentless).
@@ -11,12 +9,12 @@ Learn more on our [official website](https://docs.datadoghq.com/tests/) and chec
11
9
  ## Features
12
10
 
13
11
  - [Test Visibility](https://docs.datadoghq.com/tests/) - collect metrics and results for your tests
14
- - [Intelligent test runner](https://docs.datadoghq.com/intelligent_test_runner/) - save time by selectively running only tests affected by code changes
15
- - [Auto test retries](https://docs.datadoghq.com/tests/auto_test_retries/?tab=ruby) - retrying failing tests up to N times to avoid failing your build due to flaky tests
16
- - [Early flake detection](https://docs.datadoghq.com/tests/early_flake_detection?tab=ruby) - Datadog’s test flakiness solution that identifies flakes early by running newly added tests multiple times
12
+ - [Test impact analysis](https://docs.datadoghq.com/tests/test_impact_analysis/) - save time by selectively running only tests affected by code changes
13
+ - [Flaky test management](https://docs.datadoghq.com/tests/flaky_test_management/) - track, alert, search your flaky tests in Datadog UI
14
+ - [Auto test retries](https://docs.datadoghq.com/tests/flaky_test_management/auto_test_retries/?tab=ruby) - retrying failing tests up to N times to avoid failing your build due to flaky tests
15
+ - [Early flake detection](https://docs.datadoghq.com/tests/flaky_test_management/early_flake_detection/?tab=ruby) - Datadog’s test flakiness solution that identifies flakes early by running newly added tests multiple times
17
16
  - [Search and manage CI tests](https://docs.datadoghq.com/tests/search/)
18
17
  - [Enhance developer workflows](https://docs.datadoghq.com/tests/developer_workflows)
19
- - [Flaky test management](https://docs.datadoghq.com/tests/guides/flaky_test_management/)
20
18
  - [Add custom measures to your tests](https://docs.datadoghq.com/tests/guides/add_custom_measures/?tab=ruby)
21
19
  - [Browser tests integration with Datadog RUM](https://docs.datadoghq.com/tests/browser_tests)
22
20
 
@@ -37,7 +35,7 @@ If you used [test visibility for Ruby](https://docs.datadoghq.com/tests/setup/ru
37
35
  ## Setup
38
36
 
39
37
  - [Test visibility setup](https://docs.datadoghq.com/tests/setup/ruby/?tab=cloudciprovideragentless)
40
- - [Intelligent test runner setup](https://docs.datadoghq.com/intelligent_test_runner/setup/ruby) (test visibility setup is required before setting up intelligent test runner)
38
+ - [Test impact analysis setup](https://docs.datadoghq.com/tests/test_impact_analysis/setup/ruby/?tab=cloudciprovideragentless) (test visibility setup is required before setting up test impact analysis)
41
39
 
42
40
  ## Contributing
43
41
 
data/exe/ddcirb CHANGED
@@ -2,4 +2,6 @@
2
2
 
3
3
  require "datadog/ci/cli/cli"
4
4
 
5
- Datadog::CI::CLI.exec(ARGV.first)
5
+ command = ARGV.shift
6
+
7
+ Datadog::CI::CLI.exec(command, ARGV)
@@ -0,0 +1,8 @@
1
+ require "datadog/ci"
2
+
3
+ if RUBY_ENGINE == "jruby"
4
+ Datadog.logger.error("Auto instrumentation is not supported on JRuby. Please use manual instrumentation instead.")
5
+ return
6
+ end
7
+
8
+ Datadog::CI::Contrib::Instrumentation.auto_instrument
@@ -1,14 +1,17 @@
1
1
  require "datadog"
2
2
  require "datadog/ci"
3
3
 
4
+ require_relative "command/exec"
4
5
  require_relative "command/skippable_tests_percentage"
5
6
  require_relative "command/skippable_tests_percentage_estimate"
6
7
 
7
8
  module Datadog
8
9
  module CI
9
10
  module CLI
10
- def self.exec(action)
11
+ def self.exec(action, args = [])
11
12
  case action
13
+ when "exec"
14
+ Command::Exec.new(args).exec
12
15
  when "skipped-tests", "skippable-tests"
13
16
  Command::SkippableTestsPercentage.new.exec
14
17
  when "skipped-tests-estimate", "skippable-tests-estimate"
@@ -17,6 +20,7 @@ module Datadog
17
20
  puts("Usage: bundle exec ddcirb [command] [options]. Available commands:")
18
21
  puts(" skippable-tests - calculates the exact percentage of skipped tests and prints it to stdout or file")
19
22
  puts(" skippable-tests-estimate - estimates the percentage of skipped tests and prints it to stdout or file")
23
+ puts(" exec YOUR_TEST_COMMAND - automatically instruments your test command with Datadog and executes it")
20
24
  end
21
25
  end
22
26
  end
@@ -27,6 +27,7 @@ module Datadog
27
27
 
28
28
  opts.on("-f", "--file FILENAME", "Output result to file FILENAME")
29
29
  opts.on("--verbose", "Verbose output to stdout")
30
+ opts.on("--spec-path=[SPEC_PATH]", "Relative path to the spec directory, example: spec")
30
31
 
31
32
  command_options(opts)
32
33
  end.parse!(into: ddcirb_options)
@@ -0,0 +1,29 @@
1
+ require_relative "base"
2
+ require_relative "../../test_optimisation/skippable_percentage/estimator"
3
+
4
+ module Datadog
5
+ module CI
6
+ module CLI
7
+ module Command
8
+ class Exec < Base
9
+ def initialize(args)
10
+ super()
11
+
12
+ @args = args
13
+ end
14
+
15
+ def exec
16
+ rubyopts = [
17
+ "-rdatadog/ci/auto_instrument"
18
+ ]
19
+
20
+ existing_rubyopt = ENV["RUBYOPT"]
21
+ ENV["RUBYOPT"] = existing_rubyopt ? "#{existing_rubyopt} #{rubyopts.join(" ")}" : rubyopts.join(" ")
22
+
23
+ Kernel.exec(*@args)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -18,7 +18,6 @@ module Datadog
18
18
 
19
19
  def command_options(opts)
20
20
  opts.on("--rspec-opts=[OPTIONS]", "Command line options to pass to RSpec")
21
- opts.on("--spec-path=[SPEC_PATH]", "Relative path to the spec directory, example: spec")
22
21
  end
23
22
  end
24
23
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../contrib/instrumentation"
3
4
  require_relative "../ext/settings"
4
5
  require_relative "../utils/bundle"
5
6
 
@@ -8,8 +9,6 @@ module Datadog
8
9
  module Configuration
9
10
  # Adds CI behavior to ddtrace settings
10
11
  module Settings
11
- InvalidIntegrationError = Class.new(StandardError)
12
-
13
12
  def self.extended(base)
14
13
  base = base.singleton_class unless base.is_a?(Class)
15
14
  add_settings!(base)
@@ -126,23 +125,11 @@ module Datadog
126
125
  define_method(:instrument) do |integration_name, options = {}, &block|
127
126
  return unless enabled
128
127
 
129
- integration = fetch_integration(integration_name)
130
- integration.configure(options, &block)
131
-
132
- return unless integration.enabled
133
-
134
- patch_results = integration.patch
135
- next if patch_results == true
136
-
137
- error_message = <<-ERROR
138
- Available?: #{patch_results[:available]}, Loaded?: #{patch_results[:loaded]},
139
- Compatible?: #{patch_results[:compatible]}, Patchable?: #{patch_results[:patchable]}"
140
- ERROR
141
- Datadog.logger.warn("Unable to patch #{integration_name} (#{error_message})")
128
+ Contrib::Instrumentation.instrument(integration_name, options, &block)
142
129
  end
143
130
 
144
131
  define_method(:[]) do |integration_name|
145
- fetch_integration(integration_name).configuration
132
+ Contrib::Instrumentation.fetch_integration(integration_name).configuration
146
133
  end
147
134
 
148
135
  option :trace_flush
@@ -151,11 +138,6 @@ module Datadog
151
138
  o.type :hash
152
139
  o.default({})
153
140
  end
154
-
155
- define_method(:fetch_integration) do |name|
156
- Datadog::CI::Contrib::Integration.registry[name] ||
157
- raise(InvalidIntegrationError, "'#{name}' is not a valid integration.")
158
- end
159
141
  end
160
142
  end
161
143
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../integration"
4
+ require_relative "patcher"
5
+
6
+ module Datadog
7
+ module CI
8
+ module Contrib
9
+ module Ciqueue
10
+ # ci-queue test runner instrumentation
11
+ # https://github.com/Shopify/ci-queue
12
+ class Integration < Contrib::Integration
13
+ MINIMUM_VERSION = Gem::Version.new("0.9.0")
14
+
15
+ def version
16
+ Gem.loaded_specs["ci-queue"]&.version
17
+ end
18
+
19
+ def loaded?
20
+ !defined?(::RSpec::Queue::Runner).nil?
21
+ end
22
+
23
+ def compatible?
24
+ super && version >= MINIMUM_VERSION
25
+ end
26
+
27
+ def patcher
28
+ Patcher
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../patcher"
4
+ require_relative "../rspec/runner"
5
+
6
+ module Datadog
7
+ module CI
8
+ module Contrib
9
+ module Ciqueue
10
+ # Patcher enables patching of 'rspec' module.
11
+ module Patcher
12
+ include Datadog::CI::Contrib::Patcher
13
+
14
+ module_function
15
+
16
+ def patch
17
+ ::RSpec::Queue::Runner.include(Contrib::RSpec::Runner)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -3,6 +3,7 @@
3
3
  require_relative "../../ext/test"
4
4
  require_relative "../../git/local_repository"
5
5
  require_relative "../../utils/test_run"
6
+ require_relative "../instrumentation"
6
7
  require_relative "ext"
7
8
 
8
9
  module Datadog
@@ -38,9 +39,9 @@ module Datadog
38
39
  test_visibility_component.start_test_session(
39
40
  tags: {
40
41
  CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
41
- CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Cucumber::Integration.version.to_s
42
+ CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s
42
43
  },
43
- service: configuration[:service_name]
44
+ service: datadog_configuration[:service_name]
44
45
  )
45
46
  test_visibility_component.start_test_module(Ext::FRAMEWORK)
46
47
  end
@@ -61,7 +62,7 @@ module Datadog
61
62
  # @type var tags: Hash[String, String]
62
63
  tags = {
63
64
  CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
64
- CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Cucumber::Integration.version.to_s,
65
+ CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s,
65
66
  CI::Ext::Test::TAG_SOURCE_FILE => Git::LocalRepository.relative_to_root(event.test_case.location.file),
66
67
  CI::Ext::Test::TAG_SOURCE_START => event.test_case.location.line.to_s
67
68
  }
@@ -81,7 +82,7 @@ module Datadog
81
82
  event.test_case.name,
82
83
  test_suite_name,
83
84
  tags: tags,
84
- service: configuration[:service_name]
85
+ service: datadog_configuration[:service_name]
85
86
  )
86
87
  if event.test_case.match_tags?("@#{CI::Ext::Test::ITR_UNSKIPPABLE_OPTION}")
87
88
  test_span&.itr_unskippable!
@@ -199,7 +200,11 @@ module Datadog
199
200
  end
200
201
  end
201
202
 
202
- def configuration
203
+ def datadog_integration
204
+ CI::Contrib::Instrumentation.fetch_integration(:cucumber)
205
+ end
206
+
207
+ def datadog_configuration
203
208
  Datadog.configuration.ci[:cucumber]
204
209
  end
205
210
 
@@ -9,30 +9,21 @@ module Datadog
9
9
  module Contrib
10
10
  module Cucumber
11
11
  # Description of Cucumber integration
12
- class Integration
13
- include Datadog::CI::Contrib::Integration
14
-
12
+ class Integration < Contrib::Integration
15
13
  MINIMUM_VERSION = Gem::Version.new("3.0.0")
16
14
 
17
- register_as :cucumber
18
-
19
- def self.version
15
+ def version
20
16
  Gem.loaded_specs["cucumber"]&.version
21
17
  end
22
18
 
23
- def self.loaded?
24
- !defined?(::Cucumber).nil? && !defined?(::Cucumber::Runtime).nil?
19
+ def loaded?
20
+ !defined?(::Cucumber).nil? && !defined?(::Cucumber::Runtime).nil? && !defined?(::Cucumber::Configuration).nil?
25
21
  end
26
22
 
27
- def self.compatible?
23
+ def compatible?
28
24
  super && version >= MINIMUM_VERSION
29
25
  end
30
26
 
31
- # test environments should not auto instrument test libraries
32
- def auto_instrument?
33
- false
34
- end
35
-
36
27
  def new_configuration
37
28
  Configuration::Settings.new
38
29
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "datadog/tracing/contrib/patcher"
3
+ require_relative "../patcher"
4
4
 
5
5
  require_relative "instrumentation"
6
6
 
@@ -10,14 +10,10 @@ module Datadog
10
10
  module Cucumber
11
11
  # Patches 'cucumber' gem.
12
12
  module Patcher
13
- include Datadog::Tracing::Contrib::Patcher
13
+ include Datadog::CI::Contrib::Patcher
14
14
 
15
15
  module_function
16
16
 
17
- def target_version
18
- Integration.version
19
- end
20
-
21
17
  def patch
22
18
  ::Cucumber::Runtime.include(Instrumentation)
23
19
  end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "datadog/core/utils/only_once"
4
+
5
+ module Datadog
6
+ module CI
7
+ module Contrib
8
+ module Instrumentation
9
+ class InvalidIntegrationError < StandardError; end
10
+
11
+ @registry = {}
12
+ @auto_instrumented = false
13
+
14
+ def self.registry
15
+ @registry
16
+ end
17
+
18
+ def self.register_integration(integration_class)
19
+ @registry[integration_name(integration_class)] = integration_class.new
20
+ end
21
+
22
+ def self.auto_instrumented?
23
+ @auto_instrumented
24
+ end
25
+
26
+ # Auto instrumentation of all integrations.
27
+ #
28
+ # Registers a :script_compiled tracepoint to watch for new Ruby files being loaded.
29
+ # On every file load it checks if any of the integrations are patchable now.
30
+ # Only the integrations that are available in the environment are checked.
31
+ def self.auto_instrument
32
+ Datadog.logger.debug("Auto instrumenting all integrations...")
33
+ @auto_instrumented = true
34
+
35
+ auto_instrumented_integrations = fetch_auto_instrumented_integrations
36
+ if auto_instrumented_integrations.empty?
37
+ Datadog.logger.warn(
38
+ "Auto instrumentation was requested, but no available integrations were found. " \
39
+ "Tests will be run without Datadog instrumentation."
40
+ )
41
+ return
42
+ end
43
+
44
+ # note that `Kernel.require` might be called from a different thread, so
45
+ # there is a possibility of concurrent execution of this tracepoint
46
+ mutex = Mutex.new
47
+ script_compiled_tracepoint = TracePoint.new(:script_compiled) do |tp|
48
+ all_patched = true
49
+
50
+ mutex.synchronize do
51
+ auto_instrumented_integrations.each do |integration|
52
+ next if integration.patched?
53
+
54
+ all_patched = false
55
+ next unless integration.loaded?
56
+
57
+ auto_configure_datadog
58
+
59
+ Datadog.logger.debug("#{integration.class} is loaded")
60
+ patch_integration(integration)
61
+ end
62
+
63
+ if all_patched
64
+ Datadog.logger.debug("All expected integrations are patched, disabling the script_compiled tracepoint")
65
+
66
+ tp.disable
67
+ end
68
+ end
69
+ end
70
+ script_compiled_tracepoint.enable
71
+ end
72
+
73
+ # Manual instrumentation of a specific integration.
74
+ #
75
+ # This method is called when user has `c.ci.instrument :integration_name` in their code.
76
+ def self.instrument(integration_name, options = {}, &block)
77
+ integration = fetch_integration(integration_name)
78
+ # when manually instrumented, it might be configured via code
79
+ integration.configure(options, &block)
80
+
81
+ return unless integration.enabled
82
+
83
+ patch_integration(integration, with_dependencies: true)
84
+ end
85
+
86
+ # This method instruments all additional test libraries (ex: selenium-webdriver) that need to be instrumented
87
+ # later in the test suite run.
88
+ #
89
+ # It is intended to be called when test session starts to add additional capabilities to test visibility.
90
+ #
91
+ # This method does not automatically instrument test frameworks (ex: RSpec, Cucumber, etc), it requires
92
+ # test framework to be already instrumented.
93
+ def self.instrument_on_session_start
94
+ Datadog.logger.debug("Instrumenting all late instrumented integrations...")
95
+
96
+ @registry.each do |name, integration|
97
+ next unless integration.late_instrument?
98
+ next unless integration.enabled
99
+
100
+ Datadog.logger.debug "#{name} is allowed to be late instrumented"
101
+
102
+ patch_integration(integration)
103
+ end
104
+ end
105
+
106
+ def self.fetch_integration(name)
107
+ @registry[name] ||
108
+ raise(InvalidIntegrationError, "'#{name}' is not a valid integration.")
109
+ end
110
+
111
+ # take the parent module name and downcase it
112
+ # for example for Datadog::CI::Contrib::RSpec::Integration it will be :rspec
113
+ def self.integration_name(subclass)
114
+ result = subclass.name&.split("::")&.[](-2)&.downcase&.to_sym
115
+ raise "Integration name could not be derived for #{subclass}" if result.nil?
116
+ result
117
+ end
118
+
119
+ def self.patch_integration(integration, with_dependencies: false)
120
+ patch_results = integration.patch
121
+
122
+ if patch_results[:ok]
123
+ Datadog.logger.debug("#{integration.class} is patched")
124
+
125
+ return unless with_dependencies
126
+
127
+ # try to patch dependant integrations (for example knapsack that depends on rspec)
128
+ dependants = integration.dependants
129
+ .map { |name| fetch_integration(name) }
130
+ .filter { |integration| integration.patchable? }
131
+
132
+ Datadog.logger.debug("Found dependent integrations for #{integration.class}: #{dependants}")
133
+
134
+ dependants.each do |dependent_integration|
135
+ patch_integration(dependent_integration, with_dependencies: true)
136
+ end
137
+
138
+ else
139
+ Datadog.logger.debug("Attention: #{integration.class} is not patched (#{patch_results})")
140
+ end
141
+ end
142
+
143
+ def self.fetch_auto_instrumented_integrations
144
+ @registry.filter_map do |name, integration|
145
+ # ignore integrations that are not in the Gemfile or have incompatible versions
146
+ next unless integration.compatible?
147
+
148
+ # late instrumented integrations will be patched when the test session starts
149
+ next if integration.late_instrument?
150
+
151
+ Datadog.logger.debug("#{name} should be auto instrumented")
152
+ integration
153
+ end
154
+ end
155
+
156
+ def self.auto_configure_datadog
157
+ configure_once.run do
158
+ Datadog.logger.debug("Applying Datadog configuration in CI mode...")
159
+ Datadog.configure do |c|
160
+ c.ci.enabled = true
161
+ c.tracing.enabled = true
162
+ end
163
+ end
164
+ end
165
+
166
+ # This is not thread safe, it is synchronized by the caller in the tracepoint
167
+ def self.configure_once
168
+ @configure_once ||= Datadog::Core::Utils::OnlyOnce.new
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end