datadog-ci 1.8.1 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) 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/exec.rb +29 -0
  8. data/lib/datadog/ci/configuration/settings.rb +3 -21
  9. data/lib/datadog/ci/contrib/ciqueue/integration.rb +34 -0
  10. data/lib/datadog/ci/contrib/ciqueue/patcher.rb +23 -0
  11. data/lib/datadog/ci/contrib/cucumber/formatter.rb +10 -5
  12. data/lib/datadog/ci/contrib/cucumber/integration.rb +5 -14
  13. data/lib/datadog/ci/contrib/cucumber/patcher.rb +2 -6
  14. data/lib/datadog/ci/contrib/instrumentation.rb +173 -0
  15. data/lib/datadog/ci/contrib/integration.rb +101 -117
  16. data/lib/datadog/ci/contrib/knapsack/extension.rb +27 -0
  17. data/lib/datadog/ci/contrib/knapsack/integration.rb +36 -0
  18. data/lib/datadog/ci/contrib/knapsack/patcher.rb +29 -0
  19. data/lib/datadog/ci/contrib/knapsack/runner.rb +66 -0
  20. data/lib/datadog/ci/contrib/minitest/integration.rb +6 -14
  21. data/lib/datadog/ci/contrib/minitest/patcher.rb +1 -5
  22. data/lib/datadog/ci/contrib/minitest/runner.rb +6 -1
  23. data/lib/datadog/ci/contrib/minitest/test.rb +6 -1
  24. data/lib/datadog/ci/contrib/patcher.rb +62 -0
  25. data/lib/datadog/ci/contrib/rspec/example.rb +64 -25
  26. data/lib/datadog/ci/contrib/rspec/example_group.rb +18 -1
  27. data/lib/datadog/ci/contrib/rspec/integration.rb +10 -13
  28. data/lib/datadog/ci/contrib/rspec/patcher.rb +2 -33
  29. data/lib/datadog/ci/contrib/rspec/runner.rb +6 -1
  30. data/lib/datadog/ci/contrib/selenium/capybara_driver.rb +1 -1
  31. data/lib/datadog/ci/contrib/selenium/driver.rb +1 -1
  32. data/lib/datadog/ci/contrib/selenium/integration.rb +6 -10
  33. data/lib/datadog/ci/contrib/selenium/navigation.rb +6 -2
  34. data/lib/datadog/ci/contrib/selenium/patcher.rb +2 -6
  35. data/lib/datadog/ci/contrib/selenium/rum.rb +0 -2
  36. data/lib/datadog/ci/contrib/simplecov/integration.rb +6 -10
  37. data/lib/datadog/ci/contrib/simplecov/patcher.rb +2 -6
  38. data/lib/datadog/ci/test.rb +8 -7
  39. data/lib/datadog/ci/test_optimisation/component.rb +9 -4
  40. data/lib/datadog/ci/test_retries/strategy/retry_new.rb +1 -1
  41. data/lib/datadog/ci/test_visibility/component.rb +2 -2
  42. data/lib/datadog/ci/test_visibility/telemetry.rb +2 -1
  43. data/lib/datadog/ci/version.rb +2 -2
  44. data/lib/datadog/ci.rb +9 -1
  45. metadata +13 -7
  46. data/lib/datadog/ci/contrib/contrib.rb +0 -31
  47. data/lib/datadog/ci/contrib/rspec/knapsack_pro/extension.rb +0 -29
  48. data/lib/datadog/ci/contrib/rspec/knapsack_pro/patcher.rb +0 -26
  49. data/lib/datadog/ci/contrib/rspec/knapsack_pro/runner.rb +0 -62
@@ -1,147 +1,131 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "settings"
4
+ require_relative "instrumentation"
4
5
 
5
6
  module Datadog
6
7
  module CI
7
8
  module Contrib
8
- module Integration
9
- @registry = {}
10
-
11
- def self.included(base)
12
- base.extend(ClassMethods)
13
- base.include(InstanceMethods)
9
+ class Integration
10
+ def self.inherited(subclass)
11
+ Instrumentation.register_integration(subclass)
14
12
  end
15
13
 
16
- def self.register(klass, name)
17
- registry[name] = klass.new
14
+ # List of integrations names that depend on this integration.
15
+ # Specify when you might need to automatically instrument other integrations (like test runner for the
16
+ # test framework).
17
+ def dependants
18
+ []
18
19
  end
19
20
 
20
- def self.registry
21
- @registry
21
+ # Version of the integration target code in the environment.
22
+ #
23
+ # This is the gem version, when the instrumentation target is a Ruby gem.
24
+ #
25
+ # If the target for instrumentation has concept of versioning, override {.version},
26
+ # otherwise override {.available?} and implement a custom target presence check.
27
+ # @return [Object] the target version
28
+ def version
29
+ nil
22
30
  end
23
31
 
24
- # Class-level methods for Integration
25
- module ClassMethods
26
- def register_as(name)
27
- Integration.register(self, name)
28
- end
29
-
30
- # Version of the integration target code in the environment.
31
- #
32
- # This is the gem version, when the instrumentation target is a Ruby gem.
33
- #
34
- # If the target for instrumentation has concept of versioning, override {.version},
35
- # otherwise override {.available?} and implement a custom target presence check.
36
- # @return [Object] the target version
37
- def version
38
- nil
39
- end
32
+ # Is the target available to be instrumented? (e.g. gem installed?)
33
+ #
34
+ # The target doesn't have to be loaded (e.g. `require`) yet, but needs to be able
35
+ # to be loaded before instrumentation can commence.
36
+ #
37
+ # By default, {.available?} checks if {.version} returned a non-nil object.
38
+ #
39
+ # If the target for instrumentation has concept of versioning, override {.version},
40
+ # otherwise override {.available?} and implement a custom target presence check.
41
+ # @return [Boolean] is the target available for instrumentation in this Ruby environment?
42
+ def available?
43
+ !version.nil?
44
+ end
40
45
 
41
- # Is the target available to be instrumented? (e.g. gem installed?)
42
- #
43
- # The target doesn't have to be loaded (e.g. `require`) yet, but needs to be able
44
- # to be loaded before instrumentation can commence.
45
- #
46
- # By default, {.available?} checks if {.version} returned a non-nil object.
47
- #
48
- # If the target for instrumentation has concept of versioning, override {.version},
49
- # otherwise override {.available?} and implement a custom target presence check.
50
- # @return [Boolean] is the target available for instrumentation in this Ruby environment?
51
- def available?
52
- !version.nil?
53
- end
46
+ # Is the target loaded into the application? (e.g. gem required? Constant defined?)
47
+ #
48
+ # The target's objects should be ready to be referenced by the instrumented when {.loaded}
49
+ # returns `true`.
50
+ #
51
+ # @return [Boolean] is the target ready to be referenced during instrumentation?
52
+ def loaded?
53
+ true
54
+ end
54
55
 
55
- # Is the target loaded into the application? (e.g. gem required? Constant defined?)
56
- #
57
- # The target's objects should be ready to be referenced by the instrumented when {.loaded}
58
- # returns `true`.
59
- #
60
- # @return [Boolean] is the target ready to be referenced during instrumentation?
61
- def loaded?
62
- true
63
- end
56
+ # Is this instrumentation compatible with the available target? (e.g. minimum version met?)
57
+ # @return [Boolean] is the available target compatible with this instrumentation?
58
+ def compatible?
59
+ available?
60
+ end
64
61
 
65
- # Is this instrumentation compatible with the available target? (e.g. minimum version met?)
66
- # @return [Boolean] is the available target compatible with this instrumentation?
67
- def compatible?
68
- available?
69
- end
62
+ # Can the patch for this integration be applied?
63
+ #
64
+ # By default, this is equivalent to {#available?}, {#loaded?}, and {#compatible?}
65
+ # all being truthy.
66
+ def patchable?
67
+ available? && loaded? && compatible?
68
+ end
70
69
 
71
- # Can the patch for this integration be applied?
72
- #
73
- # By default, this is equivalent to {#available?}, {#loaded?}, and {#compatible?}
74
- # all being truthy.
75
- def patchable?
76
- available? && loaded? && compatible?
77
- end
70
+ # returns the configuration instance.
71
+ def configuration
72
+ @configuration ||= new_configuration
78
73
  end
79
74
 
80
- module InstanceMethods
81
- # returns the configuration instance.
82
- def configuration
83
- @configuration ||= new_configuration
84
- end
75
+ def configure(options = {}, &block)
76
+ configuration.configure(options, &block)
77
+ configuration
78
+ end
85
79
 
86
- def configure(options = {}, &block)
87
- configuration.configure(options, &block)
88
- configuration
89
- end
80
+ def enabled
81
+ configuration.enabled
82
+ end
90
83
 
91
- # Resets all configuration options
92
- def reset_configuration!
93
- @configuration = nil
94
- end
84
+ # The patcher module to inject instrumented objects into the instrumentation target.
85
+ #
86
+ # {Contrib::Patcher} includes the basic functionality of a patcher. `include`ing
87
+ # {Contrib::Patcher} into a new module is the recommend way to create a custom patcher.
88
+ #
89
+ # @return [Contrib::Patcher] a module that `include`s {Contrib::Patcher}
90
+ def patcher
91
+ nil
92
+ end
95
93
 
96
- def enabled
97
- configuration.enabled
94
+ # @!visibility private
95
+ def patch
96
+ if !patchable? || patcher.nil?
97
+ return {
98
+ ok: false,
99
+ available: available?,
100
+ loaded: loaded?,
101
+ compatible: compatible?,
102
+ patchable: patchable?
103
+ }
98
104
  end
99
105
 
100
- # The patcher module to inject instrumented objects into the instrumentation target.
101
- #
102
- # {Contrib::Patcher} includes the basic functionality of a patcher. `include`ing
103
- # {Contrib::Patcher} into a new module is the recommend way to create a custom patcher.
104
- #
105
- # @return [Contrib::Patcher] a module that `include`s {Contrib::Patcher}
106
- def patcher
107
- nil
108
- end
106
+ patcher.patch
107
+ {ok: true}
108
+ end
109
109
 
110
- # @!visibility private
111
- def patch
112
- # @type var patcher_klass: untyped
113
- patcher_klass = patcher
114
- if !self.class.patchable? || patcher_klass.nil?
115
- return {
116
- available: self.class.available?,
117
- loaded: self.class.loaded?,
118
- compatible: self.class.compatible?,
119
- patchable: self.class.patchable?
120
- }
121
- end
122
-
123
- patcher_klass.patch
124
- true
125
- end
110
+ def patched?
111
+ patcher&.patched?
112
+ end
126
113
 
127
- # Can the patch for this integration be applied automatically?
128
- # @return [Boolean] can the tracer activate this instrumentation without explicit user input?
129
- def auto_instrument?
130
- true
131
- end
114
+ # Can the patch for this integration be applied automatically?
115
+ # @return [Boolean] can the tracer activate this instrumentation without explicit user input?
116
+ def late_instrument?
117
+ false
118
+ end
132
119
 
133
- protected
134
-
135
- # Returns a new configuration object for this integration.
136
- #
137
- # This method normally needs to be overridden for each integration
138
- # as their settings, defaults and environment variables are
139
- # specific for each integration.
140
- #
141
- # @return [Datadog::CI::Contrib::Settings] a new, integration-specific settings object
142
- def new_configuration
143
- Datadog::CI::Contrib::Settings.new
144
- end
120
+ # Returns a new configuration object for this integration.
121
+ #
122
+ # This method normally needs to be overridden for each integration
123
+ # as their settings, defaults and environment variables are
124
+ # specific for each integration.
125
+ #
126
+ # @return [Datadog::CI::Contrib::Settings] a new, integration-specific settings object
127
+ def new_configuration
128
+ Datadog::CI::Contrib::Settings.new
145
129
  end
146
130
  end
147
131
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "knapsack_pro/extensions/rspec_extension"
4
+
5
+ require_relative "runner"
6
+
7
+ module Datadog
8
+ module CI
9
+ module Contrib
10
+ module Knapsack
11
+ module Extension
12
+ def self.included(base)
13
+ base.singleton_class.prepend(ClassMethods)
14
+ end
15
+
16
+ module ClassMethods
17
+ def setup!
18
+ super
19
+
20
+ ::RSpec::Core::Runner.include(Datadog::CI::Contrib::Knapsack::Runner)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
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 Knapsack
10
+ # Knapsack Pro test runner instrumentation
11
+ # https://github.com/KnapsackPro/knapsack_pro-ruby
12
+ class Integration < Contrib::Integration
13
+ MINIMUM_VERSION = Gem::Version.new("7.0.0")
14
+
15
+ def version
16
+ Gem.loaded_specs["knapsack_pro"]&.version
17
+ end
18
+
19
+ def loaded?
20
+ !defined?(::KnapsackPro).nil? &&
21
+ !defined?(::KnapsackPro::Extensions::RSpecExtension).nil? &&
22
+ !defined?(::KnapsackPro::Extensions::RSpecExtension::Runner).nil?
23
+ end
24
+
25
+ def compatible?
26
+ super && version >= MINIMUM_VERSION
27
+ end
28
+
29
+ def patcher
30
+ Patcher
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../patcher"
4
+
5
+ module Datadog
6
+ module CI
7
+ module Contrib
8
+ module Knapsack
9
+ module Patcher
10
+ include Datadog::CI::Contrib::Patcher
11
+
12
+ module_function
13
+
14
+ def patch
15
+ if ::RSpec::Core::Runner.ancestors.include?(::KnapsackPro::Extensions::RSpecExtension::Runner)
16
+ # knapsack already patched rspec runner
17
+ require_relative "runner"
18
+ ::RSpec::Core::Runner.include(Datadog::CI::Contrib::Knapsack::Runner)
19
+ else
20
+ # knapsack didn't patch rspec runner yet
21
+ require_relative "extension"
22
+ ::KnapsackPro::Extensions::RSpecExtension.include(Datadog::CI::Contrib::Knapsack::Extension)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../ext/test"
4
+ require_relative "../rspec/ext"
5
+ require_relative "../instrumentation"
6
+
7
+ module Datadog
8
+ module CI
9
+ module Contrib
10
+ module Knapsack
11
+ module Runner
12
+ def self.included(base)
13
+ base.prepend(InstanceMethods)
14
+ end
15
+
16
+ module InstanceMethods
17
+ # TODO: this is coupled to RSpec integration being present, not sure if it's bad or not at this point
18
+ def knapsack__run_specs(*args)
19
+ return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled]
20
+ return super unless datadog_configuration[:enabled]
21
+
22
+ test_session = test_visibility_component.start_test_session(
23
+ tags: {
24
+ CI::Ext::Test::TAG_FRAMEWORK => CI::Contrib::RSpec::Ext::FRAMEWORK,
25
+ CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s
26
+ },
27
+ service: datadog_configuration[:service_name]
28
+ )
29
+
30
+ test_module = test_visibility_component.start_test_module(CI::Contrib::RSpec::Ext::FRAMEWORK)
31
+
32
+ result = super
33
+ return result unless test_module && test_session
34
+
35
+ if result != 0
36
+ test_module.failed!
37
+ test_session.failed!
38
+ else
39
+ test_module.passed!
40
+ test_session.passed!
41
+ end
42
+ test_module.finish
43
+ test_session.finish
44
+
45
+ result
46
+ end
47
+
48
+ private
49
+
50
+ def datadog_integration
51
+ CI::Contrib::Instrumentation.fetch_integration(:rspec)
52
+ end
53
+
54
+ def datadog_configuration
55
+ Datadog.configuration.ci[:rspec]
56
+ end
57
+
58
+ def test_visibility_component
59
+ Datadog.send(:components).test_visibility
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -9,30 +9,22 @@ module Datadog
9
9
  module Contrib
10
10
  module Minitest
11
11
  # Description of Minitest integration
12
- class Integration
13
- include Datadog::CI::Contrib::Integration
14
-
12
+ class Integration < Contrib::Integration
15
13
  MINIMUM_VERSION = Gem::Version.new("5.0.0")
16
14
 
17
- register_as :minitest
18
-
19
- def self.version
15
+ def version
20
16
  Gem.loaded_specs["minitest"]&.version
21
17
  end
22
18
 
23
- def self.loaded?
24
- !defined?(::Minitest).nil?
19
+ def loaded?
20
+ !defined?(::Minitest).nil? && !defined?(::Minitest::Runnable).nil? && !defined?(::Minitest::Test).nil? &&
21
+ !defined?(::Minitest::CompositeReporter).nil?
25
22
  end
26
23
 
27
- def self.compatible?
24
+ def compatible?
28
25
  super && version >= MINIMUM_VERSION
29
26
  end
30
27
 
31
- # test environments should not auto instrument test libraries
32
- def auto_instrument?
33
- false
34
- end
35
-
36
28
  def new_configuration
37
29
  Configuration::Settings.new
38
30
  end
@@ -11,14 +11,10 @@ module Datadog
11
11
  module Minitest
12
12
  # Patcher enables patching of 'minitest' module.
13
13
  module Patcher
14
- include Datadog::Tracing::Contrib::Patcher
14
+ include Datadog::CI::Contrib::Patcher
15
15
 
16
16
  module_function
17
17
 
18
- def target_version
19
- Integration.version
20
- end
21
-
22
18
  def patch
23
19
  # test session start
24
20
  ::Minitest.include(Runner)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../../ext/test"
4
+ require_relative "../instrumentation"
4
5
  require_relative "ext"
5
6
 
6
7
  module Datadog
@@ -25,7 +26,7 @@ module Datadog
25
26
  test_visibility_component.start_test_session(
26
27
  tags: {
27
28
  CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
28
- CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Minitest::Integration.version.to_s
29
+ CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s
29
30
  },
30
31
  service: datadog_configuration[:service_name],
31
32
  total_tests_count: (DD_ESTIMATED_TESTS_PER_SUITE * ::Minitest::Runnable.runnables.size).to_i
@@ -47,6 +48,10 @@ module Datadog
47
48
 
48
49
  private
49
50
 
51
+ def datadog_integration
52
+ CI::Contrib::Instrumentation.fetch_integration(:minitest)
53
+ end
54
+
50
55
  def datadog_configuration
51
56
  Datadog.configuration.ci[:minitest]
52
57
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../../ext/test"
4
4
  require_relative "../../git/local_repository"
5
+ require_relative "../instrumentation"
5
6
  require_relative "ext"
6
7
  require_relative "helpers"
7
8
 
@@ -36,7 +37,7 @@ module Datadog
36
37
  test_suite_name,
37
38
  tags: {
38
39
  CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
39
- CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Minitest::Integration.version.to_s,
40
+ CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s,
40
41
  CI::Ext::Test::TAG_SOURCE_FILE => Git::LocalRepository.relative_to_root(source_file),
41
42
  CI::Ext::Test::TAG_SOURCE_START => line_number.to_s
42
43
  },
@@ -79,6 +80,10 @@ module Datadog
79
80
  span.finish
80
81
  end
81
82
 
83
+ def datadog_integration
84
+ CI::Contrib::Instrumentation.fetch_integration(:minitest)
85
+ end
86
+
82
87
  def datadog_configuration
83
88
  Datadog.configuration.ci[:minitest]
84
89
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "datadog/core/utils/only_once"
4
+
5
+ module Datadog
6
+ module CI
7
+ module Contrib
8
+ # Common behavior for patcher modules.
9
+ module Patcher
10
+ def self.included(base)
11
+ base.singleton_class.prepend(CommonMethods)
12
+ end
13
+
14
+ # Prepended instance methods for all patchers
15
+ module CommonMethods
16
+ attr_accessor \
17
+ :patch_error_result,
18
+ :patch_successful
19
+
20
+ def patch_name
21
+ (self.class != Class && self.class != Module) ? self.class.name : name
22
+ end
23
+
24
+ def patched?
25
+ patch_only_once.ran?
26
+ end
27
+
28
+ def patch
29
+ return unless defined?(super)
30
+
31
+ patch_only_once.run do
32
+ super.tap do
33
+ @patch_successful = true
34
+ end
35
+ rescue => e
36
+ on_patch_error(e)
37
+ end
38
+ end
39
+
40
+ # Processes patching errors. This default implementation logs the error and reports relevant metrics.
41
+ # @param e [Exception]
42
+ def on_patch_error(e)
43
+ Datadog.logger.error("Failed to apply #{patch_name} patch. Cause: #{e} Location: #{Array(e.backtrace).first}")
44
+
45
+ @patch_error_result = {
46
+ type: e.class.name,
47
+ message: e.message,
48
+ line: Array(e.backtrace).first
49
+ }
50
+ end
51
+
52
+ private
53
+
54
+ def patch_only_once
55
+ # NOTE: This is not thread-safe
56
+ @patch_only_once ||= Datadog::Core::Utils::OnlyOnce.new
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end