datadog-ci 0.5.0 → 0.6.0

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