datadog-ci 0.4.1 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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