datadog-ci 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -1
  3. data/README.md +26 -3
  4. data/lib/datadog/ci/concurrent_span.rb +59 -0
  5. data/lib/datadog/ci/configuration/components.rb +34 -4
  6. data/lib/datadog/ci/configuration/extensions.rb +21 -0
  7. data/lib/datadog/ci/configuration/settings.rb +7 -1
  8. data/lib/datadog/ci/contrib/cucumber/configuration/settings.rb +12 -1
  9. data/lib/datadog/ci/contrib/cucumber/formatter.rb +3 -4
  10. data/lib/datadog/ci/contrib/minitest/configuration/settings.rb +10 -1
  11. data/lib/datadog/ci/contrib/minitest/hooks.rb +3 -4
  12. data/lib/datadog/ci/contrib/rspec/configuration/settings.rb +10 -1
  13. data/lib/datadog/ci/contrib/rspec/example.rb +3 -4
  14. data/lib/datadog/ci/contrib/settings.rb +2 -0
  15. data/lib/datadog/ci/ext/app_types.rb +5 -0
  16. data/lib/datadog/ci/ext/settings.rb +11 -0
  17. data/lib/datadog/ci/ext/test.rb +12 -1
  18. data/lib/datadog/ci/null_span.rb +63 -0
  19. data/lib/datadog/ci/span.rb +13 -3
  20. data/lib/datadog/ci/test.rb +0 -1
  21. data/lib/datadog/ci/test_module.rb +23 -0
  22. data/lib/datadog/ci/test_session.rb +36 -0
  23. data/lib/datadog/ci/test_suite.rb +25 -0
  24. data/lib/datadog/ci/test_visibility/context/global.rb +82 -0
  25. data/lib/datadog/ci/test_visibility/context/local.rb +52 -0
  26. data/lib/datadog/ci/test_visibility/recorder.rb +293 -0
  27. data/lib/datadog/ci/test_visibility/serializers/base.rb +100 -12
  28. data/lib/datadog/ci/test_visibility/serializers/factories/test_suite_level.rb +37 -0
  29. data/lib/datadog/ci/test_visibility/serializers/span.rb +2 -16
  30. data/lib/datadog/ci/test_visibility/serializers/test_module.rb +46 -0
  31. data/lib/datadog/ci/test_visibility/serializers/test_session.rb +46 -0
  32. data/lib/datadog/ci/test_visibility/serializers/test_suite.rb +46 -0
  33. data/lib/datadog/ci/test_visibility/serializers/test_v1.rb +3 -17
  34. data/lib/datadog/ci/test_visibility/serializers/test_v2.rb +38 -0
  35. data/lib/datadog/ci/test_visibility/transport.rb +4 -4
  36. data/lib/datadog/ci/utils/test_run.rb +15 -0
  37. data/lib/datadog/ci/version.rb +1 -1
  38. data/lib/datadog/ci.rb +217 -35
  39. data/sig/datadog/ci/concurrent_span.rbs +23 -0
  40. data/sig/datadog/ci/configuration/components.rbs +4 -2
  41. data/sig/datadog/ci/configuration/extensions.rbs +9 -0
  42. data/sig/datadog/ci/ext/app_types.rbs +6 -1
  43. data/sig/datadog/ci/ext/settings.rbs +3 -0
  44. data/sig/datadog/ci/ext/test.rbs +15 -0
  45. data/sig/datadog/ci/null_span.rbs +37 -0
  46. data/sig/datadog/ci/span.rbs +4 -0
  47. data/sig/datadog/ci/test_module.rbs +6 -0
  48. data/sig/datadog/ci/test_session.rbs +9 -0
  49. data/sig/datadog/ci/test_suite.rbs +6 -0
  50. data/sig/datadog/ci/test_visibility/context/global.rbs +39 -0
  51. data/sig/datadog/ci/test_visibility/context/local.rbs +23 -0
  52. data/sig/datadog/ci/test_visibility/recorder.rbs +85 -0
  53. data/sig/datadog/ci/test_visibility/serializers/base.rbs +26 -5
  54. data/sig/datadog/ci/test_visibility/serializers/factories/test_suite_level.rbs +13 -0
  55. data/sig/datadog/ci/test_visibility/serializers/test_module.rbs +26 -0
  56. data/sig/datadog/ci/test_visibility/serializers/test_session.rbs +26 -0
  57. data/sig/datadog/ci/test_visibility/serializers/test_suite.rbs +26 -0
  58. data/sig/datadog/ci/test_visibility/serializers/test_v1.rbs +1 -1
  59. data/sig/datadog/ci/test_visibility/serializers/test_v2.rbs +25 -0
  60. data/sig/datadog/ci/test_visibility/transport.rbs +3 -3
  61. data/sig/datadog/ci/utils/test_run.rbs +11 -0
  62. data/sig/datadog/ci.rbs +19 -3
  63. metadata +32 -8
  64. data/lib/datadog/ci/context/local.rb +0 -50
  65. data/lib/datadog/ci/extensions.rb +0 -19
  66. data/lib/datadog/ci/recorder.rb +0 -119
  67. data/sig/datadog/ci/context/local.rbs +0 -21
  68. data/sig/datadog/ci/extensions.rbs +0 -7
  69. data/sig/datadog/ci/recorder.rbs +0 -30
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "ext/test"
4
+ require_relative "utils/test_run"
4
5
 
5
6
  module Datadog
6
7
  module CI
@@ -15,11 +16,21 @@ module Datadog
15
16
  @tracer_span = tracer_span
16
17
  end
17
18
 
19
+ # @return [Integer] the ID of the span.
20
+ def id
21
+ tracer_span.id
22
+ end
23
+
18
24
  # @return [String] the name of the span.
19
25
  def name
20
26
  tracer_span.name
21
27
  end
22
28
 
29
+ # @return [String] the service name of the span.
30
+ def service
31
+ tracer_span.service
32
+ end
33
+
23
34
  # @return [String] the type of the span (for example "test" or type that was provided to [Datadog::CI.trace]).
24
35
  def span_type
25
36
  tracer_span.type
@@ -83,9 +94,7 @@ module Datadog
83
94
  # @param [Hash[String, String]] tags the tags to set.
84
95
  # @return [void]
85
96
  def set_tags(tags)
86
- tags.each do |key, value|
87
- tracer_span.set_tag(key, value)
88
- end
97
+ tracer_span.set_tags(tags)
89
98
  end
90
99
 
91
100
  def set_environment_runtime_tags
@@ -93,6 +102,7 @@ module Datadog
93
102
  tracer_span.set_tag(Ext::Test::TAG_OS_PLATFORM, ::RbConfig::CONFIG["host_os"])
94
103
  tracer_span.set_tag(Ext::Test::TAG_RUNTIME_NAME, Core::Environment::Ext::LANG_ENGINE)
95
104
  tracer_span.set_tag(Ext::Test::TAG_RUNTIME_VERSION, Core::Environment::Ext::ENGINE_VERSION)
105
+ tracer_span.set_tag(Ext::Test::TAG_COMMAND, Utils::TestRun.command)
96
106
  end
97
107
 
98
108
  def set_default_tags
@@ -5,7 +5,6 @@ require_relative "span"
5
5
  module Datadog
6
6
  module CI
7
7
  # Represents a single part of a test run.
8
- # Could be a session, suite, test, or any custom span.
9
8
  #
10
9
  # @public_api
11
10
  class Test < Span
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "concurrent_span"
4
+
5
+ module Datadog
6
+ module CI
7
+ # Represents a single test module.
8
+ # Read here on what test module could mean:
9
+ # https://docs.datadoghq.com/continuous_integration/explorer/?tab=testruns#module
10
+ # This object can be shared between multiple threads.
11
+ #
12
+ # @public_api
13
+ class TestModule < ConcurrentSpan
14
+ # Finishes this test module.
15
+ # @return [void]
16
+ def finish
17
+ super
18
+
19
+ CI.deactivate_test_module
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "concurrent_span"
4
+ require_relative "ext/test"
5
+
6
+ module Datadog
7
+ module CI
8
+ # Represents the whole test session process.
9
+ # Documentation on test sessions is here:
10
+ # https://docs.datadoghq.com/continuous_integration/explorer/?tab=testruns#sessions
11
+ # This object can be shared between multiple threads.
12
+ #
13
+ # @public_api
14
+ class TestSession < ConcurrentSpan
15
+ # Finishes the current test session.
16
+ # @return [void]
17
+ def finish
18
+ super
19
+
20
+ CI.deactivate_test_session
21
+ end
22
+
23
+ def inheritable_tags
24
+ return @inheritable_tags if defined?(@inheritable_tags)
25
+
26
+ # this method is not synchronized because it does not iterate over the tags collection, but rather
27
+ # uses synchronized method to get each tag value
28
+ res = {}
29
+ Ext::Test::INHERITABLE_TAGS.each do |tag|
30
+ res[tag] = get_tag(tag)
31
+ end
32
+ @inheritable_tags = res.freeze
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "concurrent_span"
4
+
5
+ module Datadog
6
+ module CI
7
+ # Represents a single test suite.
8
+ #
9
+ # Read here on what test suite means:
10
+ # https://docs.datadoghq.com/continuous_integration/explorer/?tab=testruns#suite
11
+ #
12
+ # This object can be shared between multiple threads.
13
+ #
14
+ # @public_api
15
+ class TestSuite < ConcurrentSpan
16
+ # Finishes this test suite.
17
+ # @return [void]
18
+ def finish
19
+ super
20
+
21
+ CI.deactivate_test_suite(name)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module TestVisibility
6
+ module Context
7
+ # This context is shared between threads and represents the current test session and test module.
8
+ class Global
9
+ def initialize
10
+ # we are using Monitor instead of Mutex because it is reentrant
11
+ @mutex = Monitor.new
12
+
13
+ @test_session = nil
14
+ @test_module = nil
15
+ @test_suites = {}
16
+ end
17
+
18
+ def fetch_or_activate_test_suite(test_suite_name, &block)
19
+ @mutex.synchronize do
20
+ @test_suites[test_suite_name] ||= block.call
21
+ end
22
+ end
23
+
24
+ def fetch_or_activate_test_module(&block)
25
+ @mutex.synchronize do
26
+ @test_module ||= block.call
27
+ end
28
+ end
29
+
30
+ def fetch_or_activate_test_session(&block)
31
+ @mutex.synchronize do
32
+ @test_session ||= block.call
33
+ end
34
+ end
35
+
36
+ def active_test_module
37
+ @test_module
38
+ end
39
+
40
+ def active_test_session
41
+ @test_session
42
+ end
43
+
44
+ def active_test_suite(test_suite_name)
45
+ @mutex.synchronize { @test_suites[test_suite_name] }
46
+ end
47
+
48
+ def service
49
+ @mutex.synchronize do
50
+ # thank you RBS for this weirdness
51
+ test_session = @test_session
52
+ test_session.service if test_session
53
+ end
54
+ end
55
+
56
+ def inheritable_session_tags
57
+ @mutex.synchronize do
58
+ test_session = @test_session
59
+ if test_session
60
+ test_session.inheritable_tags
61
+ else
62
+ {}
63
+ end
64
+ end
65
+ end
66
+
67
+ def deactivate_test_session!
68
+ @mutex.synchronize { @test_session = nil }
69
+ end
70
+
71
+ def deactivate_test_module!
72
+ @mutex.synchronize { @test_module = nil }
73
+ end
74
+
75
+ def deactivate_test_suite!(test_suite_name)
76
+ @mutex.synchronize { @test_suites.delete(test_suite_name) }
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module TestVisibility
6
+ module Context
7
+ class Local
8
+ def initialize
9
+ @key = :datadog_ci_active_test
10
+
11
+ self.active_test = nil
12
+ end
13
+
14
+ def activate_test!(test)
15
+ raise "Nested tests are not supported. Currently active test: #{active_test}" unless active_test.nil?
16
+
17
+ if block_given?
18
+ begin
19
+ self.active_test = test
20
+ yield
21
+ ensure
22
+ self.active_test = nil
23
+ end
24
+ else
25
+ self.active_test = test
26
+ end
27
+ end
28
+
29
+ def deactivate_test!(test)
30
+ return if active_test.nil?
31
+
32
+ if active_test == test
33
+ self.active_test = nil
34
+ else
35
+ raise "Trying to deactivate test #{test}, but currently active test is #{active_test}"
36
+ end
37
+ end
38
+
39
+ def active_test
40
+ Thread.current[@key]
41
+ end
42
+
43
+ private
44
+
45
+ def active_test=(test)
46
+ Thread.current[@key] = test
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,293 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "datadog/tracing"
4
+ require "datadog/tracing/trace_digest"
5
+
6
+ require "rbconfig"
7
+
8
+ require_relative "context/global"
9
+ require_relative "context/local"
10
+
11
+ require_relative "../ext/app_types"
12
+ require_relative "../ext/test"
13
+ require_relative "../ext/environment"
14
+
15
+ require_relative "../span"
16
+ require_relative "../null_span"
17
+ require_relative "../test"
18
+ require_relative "../test_session"
19
+ require_relative "../test_module"
20
+ require_relative "../test_suite"
21
+
22
+ module Datadog
23
+ module CI
24
+ module TestVisibility
25
+ # Common behavior for CI tests
26
+ # Note: this class has too many responsibilities and should be split into multiple classes
27
+ class Recorder
28
+ attr_reader :environment_tags, :test_suite_level_visibility_enabled, :enabled
29
+
30
+ def initialize(enabled: true, test_suite_level_visibility_enabled: false)
31
+ @enabled = enabled
32
+ @test_suite_level_visibility_enabled = enabled && test_suite_level_visibility_enabled
33
+
34
+ @environment_tags = @enabled ? Ext::Environment.tags(ENV).freeze : {}
35
+ @local_context = Context::Local.new
36
+ @global_context = Context::Global.new
37
+ end
38
+
39
+ def start_test_session(service: nil, tags: {})
40
+ return skip_tracing unless test_suite_level_visibility_enabled
41
+
42
+ @global_context.fetch_or_activate_test_session do
43
+ tracer_span = start_datadog_tracer_span(
44
+ "test.session", build_span_options(service, Ext::AppTypes::TYPE_TEST_SESSION)
45
+ )
46
+ set_session_context(tags, tracer_span)
47
+
48
+ build_test_session(tracer_span, tags)
49
+ end
50
+ end
51
+
52
+ def start_test_module(test_module_name, service: nil, tags: {})
53
+ return skip_tracing unless test_suite_level_visibility_enabled
54
+
55
+ @global_context.fetch_or_activate_test_module do
56
+ set_inherited_globals(tags)
57
+ set_session_context(tags)
58
+
59
+ tracer_span = start_datadog_tracer_span(
60
+ test_module_name, build_span_options(service, Ext::AppTypes::TYPE_TEST_MODULE)
61
+ )
62
+ set_module_context(tags, tracer_span)
63
+
64
+ build_test_module(tracer_span, tags)
65
+ end
66
+ end
67
+
68
+ def start_test_suite(test_suite_name, service: nil, tags: {})
69
+ return skip_tracing unless test_suite_level_visibility_enabled
70
+
71
+ @global_context.fetch_or_activate_test_suite(test_suite_name) do
72
+ set_inherited_globals(tags)
73
+ set_session_context(tags)
74
+ set_module_context(tags)
75
+
76
+ tracer_span = start_datadog_tracer_span(
77
+ test_suite_name, build_span_options(service, Ext::AppTypes::TYPE_TEST_SUITE)
78
+ )
79
+ set_suite_context(tags, span: tracer_span)
80
+
81
+ build_test_suite(tracer_span, tags)
82
+ end
83
+ end
84
+
85
+ def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
86
+ return skip_tracing(block) unless enabled
87
+
88
+ set_inherited_globals(tags)
89
+ set_session_context(tags)
90
+ set_module_context(tags)
91
+ set_suite_context(tags, name: test_suite_name)
92
+
93
+ tags[Ext::Test::TAG_NAME] = test_name
94
+
95
+ span_options = build_span_options(
96
+ service,
97
+ Ext::AppTypes::TYPE_TEST,
98
+ # :resource is needed for the agent APM protocol to work correctly (for older agent versions)
99
+ # :continue_from is required to start a new trace for each test
100
+ {resource: test_name, continue_from: Datadog::Tracing::TraceDigest.new}
101
+ )
102
+
103
+ if block
104
+ start_datadog_tracer_span(test_name, span_options) do |tracer_span|
105
+ test = build_test(tracer_span, tags)
106
+
107
+ @local_context.activate_test!(test) do
108
+ block.call(test)
109
+ end
110
+ end
111
+ else
112
+ tracer_span = start_datadog_tracer_span(test_name, span_options)
113
+
114
+ test = build_test(tracer_span, tags)
115
+ @local_context.activate_test!(test)
116
+ test
117
+ end
118
+ end
119
+
120
+ def trace(span_type, span_name, tags: {}, &block)
121
+ return skip_tracing(block) unless enabled
122
+
123
+ span_options = build_span_options(
124
+ nil, # service name is completely optional for custom spans
125
+ span_type,
126
+ {resource: span_name}
127
+ )
128
+
129
+ if block
130
+ start_datadog_tracer_span(span_name, span_options) do |tracer_span|
131
+ block.call(build_span(tracer_span, tags))
132
+ end
133
+ else
134
+ tracer_span = start_datadog_tracer_span(span_name, span_options)
135
+
136
+ build_span(tracer_span, tags)
137
+ end
138
+ end
139
+
140
+ def active_span
141
+ tracer_span = Datadog::Tracing.active_span
142
+ Span.new(tracer_span) if tracer_span
143
+ end
144
+
145
+ def active_test
146
+ @local_context.active_test
147
+ end
148
+
149
+ def active_test_session
150
+ @global_context.active_test_session
151
+ end
152
+
153
+ def active_test_module
154
+ @global_context.active_test_module
155
+ end
156
+
157
+ def active_test_suite(test_suite_name)
158
+ @global_context.active_test_suite(test_suite_name)
159
+ end
160
+
161
+ def deactivate_test(test)
162
+ @local_context.deactivate_test!(test)
163
+ end
164
+
165
+ def deactivate_test_session
166
+ @global_context.deactivate_test_session!
167
+ end
168
+
169
+ def deactivate_test_module
170
+ @global_context.deactivate_test_module!
171
+ end
172
+
173
+ def deactivate_test_suite(test_suite_name)
174
+ @global_context.deactivate_test_suite!(test_suite_name)
175
+ end
176
+
177
+ private
178
+
179
+ def skip_tracing(block = nil)
180
+ if block
181
+ block.call(null_span)
182
+ else
183
+ null_span
184
+ end
185
+ end
186
+
187
+ # Sets trace's origin to ciapp-test
188
+ def set_trace_origin(trace)
189
+ trace.origin = Ext::Test::CONTEXT_ORIGIN if trace
190
+ end
191
+
192
+ def build_test_session(tracer_span, tags)
193
+ test_session = TestSession.new(tracer_span)
194
+ set_initial_tags(test_session, tags)
195
+ test_session
196
+ end
197
+
198
+ def build_test_module(tracer_span, tags)
199
+ test_module = TestModule.new(tracer_span)
200
+ set_initial_tags(test_module, tags)
201
+ test_module
202
+ end
203
+
204
+ def build_test_suite(tracer_span, tags)
205
+ test_suite = TestSuite.new(tracer_span)
206
+ set_initial_tags(test_suite, tags)
207
+ test_suite
208
+ end
209
+
210
+ def build_test(tracer_span, tags)
211
+ test = Test.new(tracer_span)
212
+ set_initial_tags(test, tags)
213
+ test
214
+ end
215
+
216
+ def build_span(tracer_span, tags)
217
+ span = Span.new(tracer_span)
218
+ set_initial_tags(span, tags)
219
+ span
220
+ end
221
+
222
+ def build_span_options(service, span_type, other_options = {})
223
+ other_options[:service] = service || @global_context.service
224
+ other_options[:span_type] = span_type
225
+
226
+ other_options
227
+ end
228
+
229
+ def set_inherited_globals(tags)
230
+ # this code achieves the same as @global_context.inheritable_session_tags.merge(tags)
231
+ # but without allocating a new hash
232
+ @global_context.inheritable_session_tags.each do |key, value|
233
+ tags[key] = value unless tags.key?(key)
234
+ end
235
+ end
236
+
237
+ def set_initial_tags(ci_span, tags)
238
+ ci_span.set_default_tags
239
+ ci_span.set_environment_runtime_tags
240
+
241
+ ci_span.set_tags(tags)
242
+ ci_span.set_tags(environment_tags)
243
+ end
244
+
245
+ def set_session_context(tags, test_session = nil)
246
+ test_session ||= active_test_session
247
+ tags[Ext::Test::TAG_TEST_SESSION_ID] = test_session.id.to_s if test_session
248
+ end
249
+
250
+ def set_module_context(tags, test_module = nil)
251
+ test_module ||= active_test_module
252
+ if test_module
253
+ tags[Ext::Test::TAG_TEST_MODULE_ID] = test_module.id.to_s
254
+ tags[Ext::Test::TAG_MODULE] = test_module.name
255
+ end
256
+ end
257
+
258
+ def set_suite_context(tags, span: nil, name: nil)
259
+ return if span.nil? && name.nil?
260
+
261
+ test_suite = span || active_test_suite(name)
262
+
263
+ if test_suite
264
+ tags[Ext::Test::TAG_TEST_SUITE_ID] = test_suite.id.to_s
265
+ tags[Ext::Test::TAG_SUITE] = test_suite.name
266
+ else
267
+ tags[Ext::Test::TAG_SUITE] = name
268
+ end
269
+ end
270
+
271
+ def start_datadog_tracer_span(span_name, span_options, &block)
272
+ if block
273
+ Datadog::Tracing.trace(span_name, **span_options) do |tracer_span, trace|
274
+ set_trace_origin(trace)
275
+
276
+ yield tracer_span
277
+ end
278
+ else
279
+ tracer_span = Datadog::Tracing.trace(span_name, **span_options)
280
+ trace = Datadog::Tracing.active_trace
281
+ set_trace_origin(trace)
282
+
283
+ tracer_span
284
+ end
285
+ end
286
+
287
+ def null_span
288
+ @null_span ||= NullSpan.new
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end