datadog-ci 0.5.0 → 0.6.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -2
  3. data/lib/datadog/ci/configuration/components.rb +13 -9
  4. data/lib/datadog/ci/configuration/extensions.rb +21 -0
  5. data/lib/datadog/ci/contrib/cucumber/configuration/settings.rb +1 -1
  6. data/lib/datadog/ci/contrib/cucumber/ext.rb +7 -5
  7. data/lib/datadog/ci/contrib/cucumber/formatter.rb +99 -19
  8. data/lib/datadog/ci/contrib/minitest/configuration/settings.rb +1 -1
  9. data/lib/datadog/ci/contrib/minitest/ext.rb +6 -4
  10. data/lib/datadog/ci/contrib/minitest/helpers.rb +22 -0
  11. data/lib/datadog/ci/contrib/minitest/hooks.rb +45 -17
  12. data/lib/datadog/ci/contrib/minitest/patcher.rb +7 -0
  13. data/lib/datadog/ci/contrib/minitest/plugin.rb +73 -0
  14. data/lib/datadog/ci/contrib/minitest/runnable.rb +42 -0
  15. data/lib/datadog/ci/contrib/rspec/configuration/settings.rb +1 -1
  16. data/lib/datadog/ci/contrib/rspec/example.rb +2 -2
  17. data/lib/datadog/ci/contrib/rspec/example_group.rb +46 -0
  18. data/lib/datadog/ci/contrib/rspec/ext.rb +5 -4
  19. data/lib/datadog/ci/contrib/rspec/integration.rb +1 -1
  20. data/lib/datadog/ci/contrib/rspec/patcher.rb +5 -0
  21. data/lib/datadog/ci/contrib/rspec/runner.rb +57 -0
  22. data/lib/datadog/ci/contrib/settings.rb +1 -1
  23. data/lib/datadog/ci/ext/test.rb +2 -0
  24. data/lib/datadog/ci/span.rb +24 -0
  25. data/lib/datadog/ci/test.rb +30 -0
  26. data/lib/datadog/ci/test_session.rb +8 -0
  27. data/lib/datadog/ci/test_visibility/context/global.rb +82 -0
  28. data/lib/datadog/ci/test_visibility/context/local.rb +52 -0
  29. data/lib/datadog/ci/test_visibility/null_recorder.rb +73 -0
  30. data/lib/datadog/ci/test_visibility/recorder.rb +314 -0
  31. data/lib/datadog/ci/version.rb +1 -1
  32. data/lib/datadog/ci.rb +3 -3
  33. data/sig/datadog/ci/configuration/components.rbs +2 -2
  34. data/sig/datadog/ci/configuration/extensions.rbs +9 -0
  35. data/sig/datadog/ci/contrib/cucumber/ext.rbs +1 -5
  36. data/sig/datadog/ci/contrib/cucumber/formatter.rbs +17 -4
  37. data/sig/datadog/ci/contrib/minitest/ext.rbs +1 -5
  38. data/sig/datadog/ci/contrib/minitest/helpers.rbs +13 -0
  39. data/sig/datadog/ci/contrib/minitest/hooks.rbs +9 -1
  40. data/sig/datadog/ci/contrib/minitest/plugin.rbs +31 -0
  41. data/sig/datadog/ci/contrib/minitest/runnable.rbs +24 -0
  42. data/sig/datadog/ci/contrib/rspec/example_group.rbs +21 -0
  43. data/sig/datadog/ci/contrib/rspec/ext.rbs +2 -8
  44. data/sig/datadog/ci/contrib/rspec/runner.rbs +21 -0
  45. data/sig/datadog/ci/ext/test.rbs +2 -0
  46. data/sig/datadog/ci/span.rbs +8 -0
  47. data/sig/datadog/ci/test.rbs +5 -0
  48. data/sig/datadog/ci/test_visibility/context/global.rbs +39 -0
  49. data/sig/datadog/ci/test_visibility/context/local.rbs +23 -0
  50. data/sig/datadog/ci/test_visibility/null_recorder.rbs +45 -0
  51. data/sig/datadog/ci/test_visibility/recorder.rbs +85 -0
  52. data/sig/datadog/ci.rbs +3 -1
  53. metadata +23 -11
  54. data/lib/datadog/ci/context/global.rb +0 -80
  55. data/lib/datadog/ci/context/local.rb +0 -50
  56. data/lib/datadog/ci/extensions.rb +0 -19
  57. data/lib/datadog/ci/recorder.rb +0 -291
  58. data/sig/datadog/ci/context/global.rbs +0 -37
  59. data/sig/datadog/ci/context/local.rbs +0 -21
  60. data/sig/datadog/ci/extensions.rbs +0 -7
  61. data/sig/datadog/ci/recorder.rbs +0 -83
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../ext/test"
4
+ require_relative "ext"
5
+
6
+ module Datadog
7
+ module CI
8
+ module Contrib
9
+ module RSpec
10
+ # Instrument RSpec::Core::Example
11
+ module ExampleGroup
12
+ def self.included(base)
13
+ base.singleton_class.prepend(ClassMethods)
14
+ end
15
+
16
+ # Instance methods for configuration
17
+ module ClassMethods
18
+ def run(reporter = ::RSpec::Core::NullReporter)
19
+ return super unless configuration[:enabled]
20
+ return super unless top_level?
21
+
22
+ test_suite = Datadog::CI.start_test_suite(file_path)
23
+
24
+ result = super
25
+
26
+ if result
27
+ test_suite.passed!
28
+ else
29
+ test_suite.failed!
30
+ end
31
+ test_suite.finish
32
+
33
+ result
34
+ end
35
+
36
+ private
37
+
38
+ def configuration
39
+ Datadog.configuration.ci[:rspec]
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -7,13 +7,14 @@ module Datadog
7
7
  # RSpec integration constants
8
8
  # TODO: mark as `@public_api` when GA, to protect from resource and tag name changes.
9
9
  module Ext
10
- APP = "rspec"
10
+ FRAMEWORK = "rspec"
11
+ DEFAULT_SERVICE_NAME = "rspec"
12
+
11
13
  ENV_ENABLED = "DD_TRACE_RSPEC_ENABLED"
14
+
15
+ # TODO: remove in 1.0
12
16
  ENV_OPERATION_NAME = "DD_TRACE_RSPEC_OPERATION_NAME"
13
- FRAMEWORK = "rspec"
14
17
  OPERATION_NAME = "rspec.example"
15
- SERVICE_NAME = "rspec"
16
- TEST_TYPE = "test"
17
18
  end
18
19
  end
19
20
  end
@@ -22,7 +22,7 @@ module Datadog
22
22
  end
23
23
 
24
24
  def self.loaded?
25
- !defined?(::RSpec).nil? && !defined?(::RSpec::Core).nil? && \
25
+ !defined?(::RSpec).nil? && !defined?(::RSpec::Core).nil? &&
26
26
  !defined?(::RSpec::Core::Example).nil?
27
27
  end
28
28
 
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "datadog/tracing/contrib/patcher"
4
+
4
5
  require_relative "example"
6
+ require_relative "example_group"
7
+ require_relative "runner"
5
8
 
6
9
  module Datadog
7
10
  module CI
@@ -19,6 +22,8 @@ module Datadog
19
22
 
20
23
  def patch
21
24
  ::RSpec::Core::Example.include(Example)
25
+ ::RSpec::Core::Runner.include(Runner)
26
+ ::RSpec::Core::ExampleGroup.include(ExampleGroup)
22
27
  end
23
28
  end
24
29
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../ext/test"
4
+ require_relative "ext"
5
+
6
+ module Datadog
7
+ module CI
8
+ module Contrib
9
+ module RSpec
10
+ # Instrument RSpec::Core::Runner
11
+ module Runner
12
+ def self.included(base)
13
+ base.prepend(InstanceMethods)
14
+ end
15
+
16
+ module InstanceMethods
17
+ def run_specs(example_groups)
18
+ return super unless configuration[:enabled]
19
+
20
+ test_session = CI.start_test_session(
21
+ tags: {
22
+ CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
23
+ CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::RSpec::Integration.version.to_s,
24
+ CI::Ext::Test::TAG_TYPE => CI::Ext::Test::TEST_TYPE
25
+ },
26
+ service: configuration[:service_name]
27
+ )
28
+
29
+ test_module = CI.start_test_module(test_session.name)
30
+
31
+ result = super
32
+
33
+ if result != 0
34
+ # TODO: repeating this twice feels clunky, we need to remove test_module API before GA
35
+ test_module.failed!
36
+ test_session.failed!
37
+ else
38
+ test_module.passed!
39
+ test_session.passed!
40
+ end
41
+ test_module.finish
42
+ test_session.finish
43
+
44
+ result
45
+ end
46
+
47
+ private
48
+
49
+ def configuration
50
+ Datadog.configuration.ci[:rspec]
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -27,7 +27,7 @@ module Datadog
27
27
  end
28
28
 
29
29
  def []=(name, value)
30
- respond_to?("#{name}=") ? send("#{name}=", value) : set_option(name, value)
30
+ respond_to?(:"#{name}=") ? send(:"#{name}=", value) : set_option(name, value)
31
31
  end
32
32
  end
33
33
  end
@@ -20,6 +20,8 @@ module Datadog
20
20
  TAG_TYPE = "test.type"
21
21
  TAG_COMMAND = "test.command"
22
22
 
23
+ TEST_TYPE = "test"
24
+
23
25
  # those tags are special and they are used to correlate tests with the test sessions, suites, and modules
24
26
  TAG_TEST_SESSION_ID = "_test.session_id"
25
27
  TAG_TEST_MODULE_ID = "_test.module_id"
@@ -36,6 +36,30 @@ module Datadog
36
36
  tracer_span.type
37
37
  end
38
38
 
39
+ # Checks whether span status is not set yet.
40
+ # @return [bool] true if span does not have status
41
+ def undefined?
42
+ tracer_span.get_tag(Ext::Test::TAG_STATUS).nil?
43
+ end
44
+
45
+ # Checks whether span status is PASS.
46
+ # @return [bool] true if span status is PASS
47
+ def passed?
48
+ tracer_span.get_tag(Ext::Test::TAG_STATUS) == Ext::Test::Status::PASS
49
+ end
50
+
51
+ # Checks whether span status is FAIL.
52
+ # @return [bool] true if span status is FAIL
53
+ def failed?
54
+ tracer_span.get_tag(Ext::Test::TAG_STATUS) == Ext::Test::Status::FAIL
55
+ end
56
+
57
+ # Checks whether span status is SKIP.
58
+ # @return [bool] true if span status is SKIP
59
+ def skipped?
60
+ tracer_span.get_tag(Ext::Test::TAG_STATUS) == Ext::Test::Status::SKIP
61
+ end
62
+
39
63
  # Sets the status of the span to "pass".
40
64
  # @return [void]
41
65
  def passed!
@@ -20,6 +20,36 @@ module Datadog
20
20
 
21
21
  CI.deactivate_test(self)
22
22
  end
23
+
24
+ # Running test suite that this test is part of (if any).
25
+ # @return [Datadog::CI::TestSuite] the test suite this test belongs to
26
+ # @return [nil] if the test suite is not found
27
+ def test_suite
28
+ suite_name = test_suite_name
29
+ CI.active_test_suite(suite_name) if suite_name
30
+ end
31
+
32
+ # Span id of the running test suite this test belongs to.
33
+ # @return [String] the span id of the test suite.
34
+ def test_suite_id
35
+ get_tag(Ext::Test::TAG_TEST_SUITE_ID)
36
+ end
37
+
38
+ def test_suite_name
39
+ get_tag(Ext::Test::TAG_SUITE)
40
+ end
41
+
42
+ # Span id of the running test module this test belongs to.
43
+ # @return [String] the span id of the test module.
44
+ def test_module_id
45
+ get_tag(Ext::Test::TAG_TEST_MODULE_ID)
46
+ end
47
+
48
+ # Span id of the running test module this test belongs to.
49
+ # @return [String] the span id of the test session.
50
+ def test_session_id
51
+ get_tag(Ext::Test::TAG_TEST_SESSION_ID)
52
+ end
23
53
  end
24
54
  end
25
55
  end
@@ -20,6 +20,14 @@ module Datadog
20
20
  CI.deactivate_test_session
21
21
  end
22
22
 
23
+ # Return the test session's name which is equal to test command used
24
+ # @return [String] the command for this test session.
25
+ def name
26
+ get_tag(Ext::Test::TAG_COMMAND)
27
+ end
28
+
29
+ # Return the test session tags that could be inherited by sub-spans
30
+ # @return [Hash] the tags to be inherited by sub-spans.
23
31
  def inheritable_tags
24
32
  return @inheritable_tags if defined?(@inheritable_tags)
25
33
 
@@ -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,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "recorder"
4
+
5
+ module Datadog
6
+ module CI
7
+ module TestVisibility
8
+ # Special recorder that does not record anything
9
+ class NullRecorder
10
+ def start_test_session(service: nil, tags: {})
11
+ skip_tracing
12
+ end
13
+
14
+ def start_test_module(test_module_name, service: nil, tags: {})
15
+ skip_tracing
16
+ end
17
+
18
+ def start_test_suite(test_suite_name, service: nil, tags: {})
19
+ skip_tracing
20
+ end
21
+
22
+ def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
23
+ skip_tracing(block)
24
+ end
25
+
26
+ def trace(span_type, span_name, tags: {}, &block)
27
+ skip_tracing(block)
28
+ end
29
+
30
+ def active_span
31
+ end
32
+
33
+ def active_test
34
+ end
35
+
36
+ def active_test_session
37
+ end
38
+
39
+ def active_test_module
40
+ end
41
+
42
+ def active_test_suite(test_suite_name)
43
+ end
44
+
45
+ def deactivate_test(test)
46
+ end
47
+
48
+ def deactivate_test_session
49
+ end
50
+
51
+ def deactivate_test_module
52
+ end
53
+
54
+ def deactivate_test_suite(test_suite_name)
55
+ end
56
+
57
+ private
58
+
59
+ def skip_tracing(block = nil)
60
+ if block
61
+ block.call(null_span)
62
+ else
63
+ null_span
64
+ end
65
+ end
66
+
67
+ def null_span
68
+ @null_span ||= NullSpan.new
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end