datadog-ci 0.4.0 → 0.5.0

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 (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