datadog-ci 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -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 +37 -3
  6. data/lib/datadog/ci/configuration/settings.rb +7 -1
  7. data/lib/datadog/ci/context/global.rb +80 -0
  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/recorder.rb +207 -35
  20. data/lib/datadog/ci/span.rb +13 -3
  21. data/lib/datadog/ci/test.rb +0 -1
  22. data/lib/datadog/ci/test_module.rb +23 -0
  23. data/lib/datadog/ci/test_session.rb +36 -0
  24. data/lib/datadog/ci/test_suite.rb +25 -0
  25. data/lib/datadog/ci/test_visibility/serializers/base.rb +100 -12
  26. data/lib/datadog/ci/test_visibility/serializers/factories/test_suite_level.rb +37 -0
  27. data/lib/datadog/ci/test_visibility/serializers/span.rb +2 -16
  28. data/lib/datadog/ci/test_visibility/serializers/test_module.rb +46 -0
  29. data/lib/datadog/ci/test_visibility/serializers/test_session.rb +46 -0
  30. data/lib/datadog/ci/test_visibility/serializers/test_suite.rb +46 -0
  31. data/lib/datadog/ci/test_visibility/serializers/test_v1.rb +3 -17
  32. data/lib/datadog/ci/test_visibility/serializers/test_v2.rb +38 -0
  33. data/lib/datadog/ci/test_visibility/transport.rb +4 -4
  34. data/lib/datadog/ci/utils/test_run.rb +15 -0
  35. data/lib/datadog/ci/version.rb +1 -1
  36. data/lib/datadog/ci.rb +214 -32
  37. data/sig/datadog/ci/concurrent_span.rbs +23 -0
  38. data/sig/datadog/ci/configuration/components.rbs +2 -0
  39. data/sig/datadog/ci/context/global.rbs +37 -0
  40. data/sig/datadog/ci/ext/app_types.rbs +6 -1
  41. data/sig/datadog/ci/ext/settings.rbs +3 -0
  42. data/sig/datadog/ci/ext/test.rbs +15 -0
  43. data/sig/datadog/ci/null_span.rbs +37 -0
  44. data/sig/datadog/ci/recorder.rbs +54 -1
  45. data/sig/datadog/ci/span.rbs +4 -0
  46. data/sig/datadog/ci/test_module.rbs +6 -0
  47. data/sig/datadog/ci/test_session.rbs +9 -0
  48. data/sig/datadog/ci/test_suite.rbs +6 -0
  49. data/sig/datadog/ci/test_visibility/serializers/base.rbs +26 -5
  50. data/sig/datadog/ci/test_visibility/serializers/factories/test_suite_level.rbs +13 -0
  51. data/sig/datadog/ci/test_visibility/serializers/test_module.rbs +26 -0
  52. data/sig/datadog/ci/test_visibility/serializers/test_session.rbs +26 -0
  53. data/sig/datadog/ci/test_visibility/serializers/test_suite.rbs +26 -0
  54. data/sig/datadog/ci/test_visibility/serializers/test_v1.rbs +1 -1
  55. data/sig/datadog/ci/test_visibility/serializers/test_v2.rbs +25 -0
  56. data/sig/datadog/ci/test_visibility/transport.rbs +3 -3
  57. data/sig/datadog/ci/utils/test_run.rbs +11 -0
  58. data/sig/datadog/ci.rbs +18 -2
  59. metadata +26 -2
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "datadog/tracing/span_operation"
4
+
5
+ module Datadog
6
+ module CI
7
+ # Represents an ignored span when CI visibility is disabled.
8
+ # Replaces all methods with no-op.
9
+ #
10
+ # @public_api
11
+ class NullSpan < Span
12
+ def initialize
13
+ super(Datadog::Tracing::SpanOperation.new("null.span"))
14
+ end
15
+
16
+ def id
17
+ end
18
+
19
+ def name
20
+ end
21
+
22
+ def service
23
+ end
24
+
25
+ def span_type
26
+ end
27
+
28
+ def passed!
29
+ end
30
+
31
+ def failed!(exception: nil)
32
+ end
33
+
34
+ def skipped!(exception: nil, reason: nil)
35
+ end
36
+
37
+ def get_tag(key)
38
+ end
39
+
40
+ def set_tag(key, value)
41
+ end
42
+
43
+ def set_metric(key, value)
44
+ end
45
+
46
+ def finish
47
+ end
48
+
49
+ def set_tags(tags)
50
+ end
51
+
52
+ def set_environment_runtime_tags
53
+ end
54
+
55
+ def set_default_tags
56
+ end
57
+
58
+ def to_s
59
+ self.class.to_s
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "datadog/tracing"
4
+ require "datadog/tracing/trace_digest"
4
5
 
5
6
  require "rbconfig"
6
7
 
@@ -8,36 +9,98 @@ require_relative "ext/app_types"
8
9
  require_relative "ext/test"
9
10
  require_relative "ext/environment"
10
11
 
12
+ require_relative "context/global"
11
13
  require_relative "context/local"
12
14
 
13
15
  require_relative "span"
16
+ require_relative "null_span"
14
17
  require_relative "test"
18
+ require_relative "test_session"
19
+ require_relative "test_module"
20
+ require_relative "test_suite"
15
21
 
16
22
  module Datadog
17
23
  module CI
18
24
  # Common behavior for CI tests
25
+ # Note: this class has too many responsibilities and should be split into multiple classes
19
26
  class Recorder
20
- attr_reader :environment_tags
27
+ attr_reader :environment_tags, :test_suite_level_visibility_enabled, :enabled
28
+
29
+ def initialize(enabled: true, test_suite_level_visibility_enabled: false)
30
+ @enabled = enabled
31
+ @test_suite_level_visibility_enabled = enabled && test_suite_level_visibility_enabled
21
32
 
22
- def initialize
23
33
  @environment_tags = Ext::Environment.tags(ENV).freeze
24
34
  @local_context = Context::Local.new
35
+ @global_context = Context::Global.new
36
+ end
37
+
38
+ def start_test_session(service: nil, tags: {})
39
+ return skip_tracing unless test_suite_level_visibility_enabled
40
+
41
+ @global_context.fetch_or_activate_test_session do
42
+ tracer_span = start_datadog_tracer_span(
43
+ "test.session", build_span_options(service, Ext::AppTypes::TYPE_TEST_SESSION)
44
+ )
45
+ set_session_context(tags, tracer_span)
46
+
47
+ build_test_session(tracer_span, tags)
48
+ end
49
+ end
50
+
51
+ def start_test_module(test_module_name, service: nil, tags: {})
52
+ return skip_tracing unless test_suite_level_visibility_enabled
53
+
54
+ @global_context.fetch_or_activate_test_module do
55
+ set_inherited_globals(tags)
56
+ set_session_context(tags)
57
+
58
+ tracer_span = start_datadog_tracer_span(
59
+ test_module_name, build_span_options(service, Ext::AppTypes::TYPE_TEST_MODULE)
60
+ )
61
+ set_module_context(tags, tracer_span)
62
+
63
+ build_test_module(tracer_span, tags)
64
+ end
25
65
  end
26
66
 
27
- # Creates a new span for a CI test
28
- def trace_test(test_name, service_name: nil, operation_name: "test", tags: {}, &block)
29
- span_options = {
30
- resource: test_name,
31
- service: service_name,
32
- span_type: Ext::AppTypes::TYPE_TEST
33
- }
67
+ def start_test_suite(test_suite_name, service: nil, tags: {})
68
+ return skip_tracing unless test_suite_level_visibility_enabled
69
+
70
+ @global_context.fetch_or_activate_test_suite(test_suite_name) do
71
+ set_inherited_globals(tags)
72
+ set_session_context(tags)
73
+ set_module_context(tags)
74
+
75
+ tracer_span = start_datadog_tracer_span(
76
+ test_suite_name, build_span_options(service, Ext::AppTypes::TYPE_TEST_SUITE)
77
+ )
78
+ set_suite_context(tags, span: tracer_span)
79
+
80
+ build_test_suite(tracer_span, tags)
81
+ end
82
+ end
83
+
84
+ def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
85
+ return skip_tracing(block) unless enabled
86
+
87
+ set_inherited_globals(tags)
88
+ set_session_context(tags)
89
+ set_module_context(tags)
90
+ set_suite_context(tags, name: test_suite_name)
34
91
 
35
92
  tags[Ext::Test::TAG_NAME] = test_name
36
93
 
37
- if block
38
- Datadog::Tracing.trace(operation_name, **span_options) do |tracer_span, trace|
39
- set_trace_origin(trace)
94
+ span_options = build_span_options(
95
+ service,
96
+ Ext::AppTypes::TYPE_TEST,
97
+ # :resource is needed for the agent APM protocol to work correctly (for older agent versions)
98
+ # :continue_from is required to start a new trace for each test
99
+ {resource: test_name, continue_from: Datadog::Tracing::TraceDigest.new}
100
+ )
40
101
 
102
+ if block
103
+ start_datadog_tracer_span(test_name, span_options) do |tracer_span|
41
104
  test = build_test(tracer_span, tags)
42
105
 
43
106
  @local_context.activate_test!(test) do
@@ -45,10 +108,7 @@ module Datadog
45
108
  end
46
109
  end
47
110
  else
48
- tracer_span = Datadog::Tracing.trace(operation_name, **span_options)
49
- trace = Datadog::Tracing.active_trace
50
-
51
- set_trace_origin(trace)
111
+ tracer_span = start_datadog_tracer_span(test_name, span_options)
52
112
 
53
113
  test = build_test(tracer_span, tags)
54
114
  @local_context.activate_test!(test)
@@ -57,62 +117,174 @@ module Datadog
57
117
  end
58
118
 
59
119
  def trace(span_type, span_name, tags: {}, &block)
60
- span_options = {
61
- resource: span_name,
62
- span_type: span_type
63
- }
120
+ return skip_tracing(block) unless enabled
121
+
122
+ span_options = build_span_options(
123
+ nil, # service name is completely optional for custom spans
124
+ span_type,
125
+ {resource: span_name}
126
+ )
64
127
 
65
128
  if block
66
- Datadog::Tracing.trace(span_name, **span_options) do |tracer_span, trace|
129
+ start_datadog_tracer_span(span_name, span_options) do |tracer_span|
67
130
  block.call(build_span(tracer_span, tags))
68
131
  end
69
132
  else
70
- tracer_span = Datadog::Tracing.trace(span_name, **span_options)
133
+ tracer_span = start_datadog_tracer_span(span_name, span_options)
71
134
 
72
135
  build_span(tracer_span, tags)
73
136
  end
74
137
  end
75
138
 
139
+ def active_span
140
+ tracer_span = Datadog::Tracing.active_span
141
+ Span.new(tracer_span) if tracer_span
142
+ end
143
+
76
144
  def active_test
77
145
  @local_context.active_test
78
146
  end
79
147
 
148
+ def active_test_session
149
+ @global_context.active_test_session
150
+ end
151
+
152
+ def active_test_module
153
+ @global_context.active_test_module
154
+ end
155
+
156
+ def active_test_suite(test_suite_name)
157
+ @global_context.active_test_suite(test_suite_name)
158
+ end
159
+
80
160
  def deactivate_test(test)
81
161
  @local_context.deactivate_test!(test)
82
162
  end
83
163
 
84
- def active_span
85
- tracer_span = Datadog::Tracing.active_span
86
- Span.new(tracer_span) if tracer_span
164
+ def deactivate_test_session
165
+ @global_context.deactivate_test_session!
166
+ end
167
+
168
+ def deactivate_test_module
169
+ @global_context.deactivate_test_module!
170
+ end
171
+
172
+ def deactivate_test_suite(test_suite_name)
173
+ @global_context.deactivate_test_suite!(test_suite_name)
87
174
  end
88
175
 
89
176
  private
90
177
 
178
+ def skip_tracing(block = nil)
179
+ if block
180
+ block.call(null_span)
181
+ else
182
+ null_span
183
+ end
184
+ end
185
+
91
186
  # Sets trace's origin to ciapp-test
92
187
  def set_trace_origin(trace)
93
188
  trace.origin = Ext::Test::CONTEXT_ORIGIN if trace
94
189
  end
95
190
 
96
- def build_test(tracer_span, tags)
97
- test = Test.new(tracer_span)
191
+ def build_test_session(tracer_span, tags)
192
+ test_session = TestSession.new(tracer_span)
193
+ set_initial_tags(test_session, tags)
194
+ test_session
195
+ end
98
196
 
99
- test.set_default_tags
100
- test.set_environment_runtime_tags
197
+ def build_test_module(tracer_span, tags)
198
+ test_module = TestModule.new(tracer_span)
199
+ set_initial_tags(test_module, tags)
200
+ test_module
201
+ end
101
202
 
102
- test.set_tags(tags)
103
- test.set_tags(environment_tags)
203
+ def build_test_suite(tracer_span, tags)
204
+ test_suite = TestSuite.new(tracer_span)
205
+ set_initial_tags(test_suite, tags)
206
+ test_suite
207
+ end
104
208
 
209
+ def build_test(tracer_span, tags)
210
+ test = Test.new(tracer_span)
211
+ set_initial_tags(test, tags)
105
212
  test
106
213
  end
107
214
 
108
215
  def build_span(tracer_span, tags)
109
216
  span = Span.new(tracer_span)
217
+ set_initial_tags(span, tags)
218
+ span
219
+ end
110
220
 
111
- span.set_default_tags
112
- span.set_environment_runtime_tags
113
- span.set_tags(tags)
221
+ def build_span_options(service, span_type, other_options = {})
222
+ other_options[:service] = service || @global_context.service
223
+ other_options[:span_type] = span_type
114
224
 
115
- span
225
+ other_options
226
+ end
227
+
228
+ def set_inherited_globals(tags)
229
+ # this code achieves the same as @global_context.inheritable_session_tags.merge(tags)
230
+ # but without allocating a new hash
231
+ @global_context.inheritable_session_tags.each do |key, value|
232
+ tags[key] = value unless tags.key?(key)
233
+ end
234
+ end
235
+
236
+ def set_initial_tags(ci_span, tags)
237
+ ci_span.set_default_tags
238
+ ci_span.set_environment_runtime_tags
239
+
240
+ ci_span.set_tags(tags)
241
+ ci_span.set_tags(environment_tags)
242
+ end
243
+
244
+ def set_session_context(tags, test_session = nil)
245
+ test_session ||= active_test_session
246
+ tags[Ext::Test::TAG_TEST_SESSION_ID] = test_session.id.to_s if test_session
247
+ end
248
+
249
+ def set_module_context(tags, test_module = nil)
250
+ test_module ||= active_test_module
251
+ if test_module
252
+ tags[Ext::Test::TAG_TEST_MODULE_ID] = test_module.id.to_s
253
+ tags[Ext::Test::TAG_MODULE] = test_module.name
254
+ end
255
+ end
256
+
257
+ def set_suite_context(tags, span: nil, name: nil)
258
+ return if span.nil? && name.nil?
259
+
260
+ test_suite = span || active_test_suite(name)
261
+
262
+ if test_suite
263
+ tags[Ext::Test::TAG_TEST_SUITE_ID] = test_suite.id.to_s
264
+ tags[Ext::Test::TAG_SUITE] = test_suite.name
265
+ else
266
+ tags[Ext::Test::TAG_SUITE] = name
267
+ end
268
+ end
269
+
270
+ def start_datadog_tracer_span(span_name, span_options, &block)
271
+ if block
272
+ Datadog::Tracing.trace(span_name, **span_options) do |tracer_span, trace|
273
+ set_trace_origin(trace)
274
+
275
+ yield tracer_span
276
+ end
277
+ else
278
+ tracer_span = Datadog::Tracing.trace(span_name, **span_options)
279
+ trace = Datadog::Tracing.active_trace
280
+ set_trace_origin(trace)
281
+
282
+ tracer_span
283
+ end
284
+ end
285
+
286
+ def null_span
287
+ @null_span ||= NullSpan.new
116
288
  end
117
289
  end
118
290
  end
@@ -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
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
5
+ require_relative "../../ext/test"
6
+
3
7
  module Datadog
4
8
  module CI
5
9
  module TestVisibility
@@ -9,11 +13,31 @@ module Datadog
9
13
  MINIMUM_DURATION_NANO = 0
10
14
  MAXIMUM_DURATION_NANO = 9223372036854775807
11
15
 
12
- attr_reader :trace, :span
16
+ CONTENT_FIELDS = [
17
+ "name", "resource", "service",
18
+ "error", "start", "duration",
19
+ "meta", "metrics",
20
+ "type" => "span_type"
21
+ ].freeze
22
+
23
+ REQUIRED_FIELDS = [
24
+ "error",
25
+ "name",
26
+ "resource",
27
+ "start",
28
+ "duration"
29
+ ].freeze
30
+
31
+ attr_reader :trace, :span, :meta
13
32
 
14
33
  def initialize(trace, span)
15
34
  @trace = trace
16
35
  @span = span
36
+
37
+ @meta = @span.meta.reject { |key, _| Ext::Test::SPECIAL_TAGS.include?(key) }
38
+
39
+ @errors = {}
40
+ @validated = false
17
41
  end
18
42
 
19
43
  def to_msgpack(packer = nil)
@@ -40,7 +64,23 @@ module Datadog
40
64
 
41
65
  # validates according to citestcycle json schema
42
66
  def valid?
43
- required_fields_present? && valid_start_time? && valid_duration?
67
+ validate! unless @validated
68
+
69
+ @errors.empty?
70
+ end
71
+
72
+ def validate!
73
+ @errors.clear
74
+
75
+ validate_required_fields!
76
+ validate_start_time!
77
+ validate_duration!
78
+
79
+ @validated = true
80
+ end
81
+
82
+ def validation_errors
83
+ @errors
44
84
  end
45
85
 
46
86
  def content_fields
@@ -67,6 +107,18 @@ module Datadog
67
107
  @span.parent_id
68
108
  end
69
109
 
110
+ def test_session_id
111
+ to_integer(@span.get_tag(Ext::Test::TAG_TEST_SESSION_ID))
112
+ end
113
+
114
+ def test_module_id
115
+ to_integer(@span.get_tag(Ext::Test::TAG_TEST_MODULE_ID))
116
+ end
117
+
118
+ def test_suite_id
119
+ to_integer(@span.get_tag(Ext::Test::TAG_TEST_SUITE_ID))
120
+ end
121
+
70
122
  def type
71
123
  end
72
124
 
@@ -98,10 +150,6 @@ module Datadog
98
150
  @duration ||= duration_nano(@span.duration)
99
151
  end
100
152
 
101
- def meta
102
- @span.meta
103
- end
104
-
105
153
  def metrics
106
154
  @span.metrics
107
155
  end
@@ -122,16 +170,48 @@ module Datadog
122
170
 
123
171
  private
124
172
 
125
- def valid_start_time?
126
- !start.nil? && start >= MINIMUM_TIMESTAMP_NANO
173
+ def validate_start_time!
174
+ validate_required!("start")
175
+ validate_greater_than_or_equal!("start", MINIMUM_TIMESTAMP_NANO)
176
+ end
177
+
178
+ def validate_duration!
179
+ validate_required!("duration")
180
+ validate_greater_than_or_equal!("duration", MINIMUM_DURATION_NANO)
181
+ validate_less_than_or_equal!("duration", MAXIMUM_DURATION_NANO)
182
+ end
183
+
184
+ def validate_required_fields!
185
+ required_fields.each do |field|
186
+ validate_required!(field)
187
+ end
127
188
  end
128
189
 
129
- def valid_duration?
130
- !duration.nil? && duration >= MINIMUM_DURATION_NANO && duration <= MAXIMUM_DURATION_NANO
190
+ def validate_required!(field)
191
+ if send(field).nil?
192
+ add_error(field, "is required")
193
+ end
131
194
  end
132
195
 
133
- def required_fields_present?
134
- required_fields.all? { |field| !send(field).nil? }
196
+ def validate_greater_than_or_equal!(field, value)
197
+ return if send(field).nil?
198
+
199
+ if send(field) < value
200
+ add_error(field, "must be greater than or equal to #{value}")
201
+ end
202
+ end
203
+
204
+ def validate_less_than_or_equal!(field, value)
205
+ return if send(field).nil?
206
+
207
+ if send(field) > value
208
+ add_error(field, "must be less than or equal to #{value}")
209
+ end
210
+ end
211
+
212
+ def add_error(field, message)
213
+ @errors[field] ||= Set.new
214
+ @errors[field] << message
135
215
  end
136
216
 
137
217
  def required_fields
@@ -154,6 +234,14 @@ module Datadog
154
234
  def duration_nano(duration)
155
235
  (duration * 1e9).to_i
156
236
  end
237
+
238
+ def to_s
239
+ "#{self.class.name}(id:#{span_id},name:#{name})"
240
+ end
241
+
242
+ def to_integer(value)
243
+ value.to_i if value
244
+ end
157
245
  end
158
246
  end
159
247
  end