datadog-ci 1.11.0 → 1.13.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -2
  3. data/lib/datadog/ci/configuration/components.rb +27 -15
  4. data/lib/datadog/ci/configuration/settings.rb +12 -0
  5. data/lib/datadog/ci/contrib/cucumber/instrumentation.rb +5 -2
  6. data/lib/datadog/ci/contrib/cuprite/configuration/settings.rb +34 -0
  7. data/lib/datadog/ci/contrib/cuprite/driver.rb +94 -0
  8. data/lib/datadog/ci/contrib/cuprite/ext.rb +15 -0
  9. data/lib/datadog/ci/contrib/cuprite/integration.rb +44 -0
  10. data/lib/datadog/ci/contrib/cuprite/patcher.rb +24 -0
  11. data/lib/datadog/ci/contrib/cuprite/script_executor.rb +32 -0
  12. data/lib/datadog/ci/contrib/minitest/test.rb +3 -3
  13. data/lib/datadog/ci/contrib/rspec/example.rb +3 -3
  14. data/lib/datadog/ci/contrib/selenium/capybara_driver.rb +4 -4
  15. data/lib/datadog/ci/contrib/selenium/configuration/settings.rb +3 -1
  16. data/lib/datadog/ci/contrib/selenium/driver.rb +4 -4
  17. data/lib/datadog/ci/contrib/selenium/ext.rb +0 -15
  18. data/lib/datadog/ci/contrib/selenium/navigation.rb +2 -2
  19. data/lib/datadog/ci/ext/app_types.rb +1 -1
  20. data/lib/datadog/ci/ext/environment/providers/github_actions.rb +3 -0
  21. data/lib/datadog/ci/ext/environment.rb +4 -0
  22. data/lib/datadog/ci/ext/rum.rb +26 -0
  23. data/lib/datadog/ci/ext/settings.rb +2 -0
  24. data/lib/datadog/ci/ext/telemetry.rb +17 -5
  25. data/lib/datadog/ci/ext/test.rb +42 -4
  26. data/lib/datadog/ci/ext/transport.rb +6 -0
  27. data/lib/datadog/ci/remote/component.rb +11 -1
  28. data/lib/datadog/ci/remote/library_settings.rb +31 -0
  29. data/lib/datadog/ci/remote/library_settings_client.rb +2 -1
  30. data/lib/datadog/ci/test.rb +67 -9
  31. data/lib/datadog/ci/test_management/component.rb +61 -0
  32. data/lib/datadog/ci/test_management/null_component.rb +25 -0
  33. data/lib/datadog/ci/test_management/tests_properties.rb +128 -0
  34. data/lib/datadog/ci/test_optimisation/component.rb +5 -3
  35. data/lib/datadog/ci/test_retries/component.rb +37 -7
  36. data/lib/datadog/ci/test_retries/driver/base.rb +5 -0
  37. data/lib/datadog/ci/test_retries/driver/retry_failed.rb +4 -0
  38. data/lib/datadog/ci/test_retries/driver/retry_flaky_fixed.rb +38 -0
  39. data/lib/datadog/ci/test_retries/driver/retry_new.rb +2 -7
  40. data/lib/datadog/ci/test_retries/strategy/retry_flaky_fixed.rb +43 -0
  41. data/lib/datadog/ci/test_retries/strategy/retry_new.rb +4 -46
  42. data/lib/datadog/ci/test_suite.rb +21 -2
  43. data/lib/datadog/ci/test_visibility/capabilities.rb +36 -0
  44. data/lib/datadog/ci/test_visibility/component.rb +70 -2
  45. data/lib/datadog/ci/test_visibility/context.rb +0 -4
  46. data/lib/datadog/ci/{test_retries/unique_tests_client.rb → test_visibility/known_tests.rb} +10 -10
  47. data/lib/datadog/ci/test_visibility/null_component.rb +3 -0
  48. data/lib/datadog/ci/test_visibility/serializers/factories/test_level.rb +1 -1
  49. data/lib/datadog/ci/test_visibility/telemetry.rb +11 -2
  50. data/lib/datadog/ci/test_visibility/transport.rb +29 -3
  51. data/lib/datadog/ci/utils/rum.rb +42 -0
  52. data/lib/datadog/ci/version.rb +1 -1
  53. data/lib/datadog/ci.rb +14 -5
  54. metadata +20 -10
  55. data/lib/datadog/ci/contrib/selenium/rum.rb +0 -43
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "datadog/core/telemetry/logging"
4
+
3
5
  require_relative "git"
4
6
  require_relative "environment/extractor"
5
7
 
@@ -73,6 +75,7 @@ module Datadog
73
75
  return if !repo_url.nil? && !repo_url.empty?
74
76
 
75
77
  Datadog.logger.error("DD_GIT_REPOSITORY_URL is not set or empty; no repo URL was automatically extracted")
78
+ Core::Telemetry::Logger.error("DD_GIT_REPOSITORY_URL is not set or empty; no repo URL was automatically extracted")
76
79
  end
77
80
 
78
81
  def validate_git_sha(git_sha)
@@ -89,6 +92,7 @@ module Datadog
89
92
  end
90
93
 
91
94
  Datadog.logger.error(message)
95
+ Core::Telemetry::Logger.error(message)
92
96
  end
93
97
  end
94
98
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module Ext
6
+ # Defines constants for Git tags
7
+ module RUM
8
+ ENV_RUM_FLUSH_WAIT_MILLIS = "DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS"
9
+
10
+ COOKIE_TEST_EXECUTION_ID = "datadog-ci-visibility-test-execution-id"
11
+
12
+ SCRIPT_IS_RUM_ACTIVE = <<~JS
13
+ return !!window.DD_RUM
14
+ JS
15
+ SCRIPT_STOP_RUM_SESSION = <<~JS
16
+ if (window.DD_RUM && window.DD_RUM.stopSession) {
17
+ window.DD_RUM.stopSession();
18
+ return true;
19
+ } else {
20
+ return false;
21
+ }
22
+ JS
23
+ end
24
+ end
25
+ end
26
+ end
@@ -20,6 +20,8 @@ module Datadog
20
20
  ENV_RETRY_FAILED_TESTS_TOTAL_LIMIT = "DD_CIVISIBILITY_TOTAL_FLAKY_RETRY_COUNT"
21
21
  ENV_RETRY_NEW_TESTS_ENABLED = "DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED"
22
22
  ENV_TEST_SESSION_NAME = "DD_TEST_SESSION_NAME"
23
+ ENV_TEST_MANAGEMENT_ENABLED = "DD_TEST_MANAGEMENT_ENABLED"
24
+ ENV_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES = "DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES"
23
25
 
24
26
  # Source: https://docs.datadoghq.com/getting_started/site/
25
27
  DD_SITE_ALLOWLIST = %w[
@@ -54,11 +54,17 @@ module Datadog
54
54
  METRIC_CODE_COVERAGE_IS_EMPTY = "code_coverage.is_empty"
55
55
  METRIC_CODE_COVERAGE_FILES = "code_coverage.files"
56
56
 
57
- METRIC_EFD_UNIQUE_TESTS_REQUEST = "early_flake_detection.request"
58
- METRIC_EFD_UNIQUE_TESTS_REQUEST_MS = "early_flake_detection.request_ms"
59
- METRIC_EFD_UNIQUE_TESTS_REQUEST_ERRORS = "early_flake_detection.request_errors"
60
- METRIC_EFD_UNIQUE_TESTS_RESPONSE_BYTES = "early_flake_detection.response_bytes"
61
- METRIC_EFD_UNIQUE_TESTS_RESPONSE_TESTS = "early_flake_detection.response_tests"
57
+ METRIC_KNOWN_TESTS_REQUEST = "known_tests.request"
58
+ METRIC_KNOWN_TESTS_REQUEST_MS = "known_tests.request_ms"
59
+ METRIC_KNOWN_TESTS_REQUEST_ERRORS = "known_tests.request_errors"
60
+ METRIC_KNOWN_TESTS_RESPONSE_BYTES = "known_tests.response_bytes"
61
+ METRIC_KNOWN_TESTS_RESPONSE_TESTS = "known_tests.response_tests"
62
+
63
+ METRIC_TEST_MANAGEMENT_TESTS_REQUEST = "test_management_tests.request"
64
+ METRIC_TEST_MANAGEMENT_TESTS_REQUEST_MS = "test_management_tests.request_ms"
65
+ METRIC_TEST_MANAGEMENT_TESTS_REQUEST_ERRORS = "test_management_tests.request_errors"
66
+ METRIC_TEST_MANAGEMENT_TESTS_RESPONSE_BYTES = "test_management_tests.response_bytes"
67
+ METRIC_TEST_MANAGEMENT_TESTS_RESPONSE_TESTS = "test_management_tests.response_tests"
62
68
 
63
69
  METRIC_TEST_SESSION = "test_session"
64
70
 
@@ -69,6 +75,7 @@ module Datadog
69
75
  TAG_BROWSER_DRIVER = "browser_driver"
70
76
  TAG_IS_RUM = "is_rum"
71
77
  TAG_IS_RETRY = "is_retry"
78
+ TAG_RETRY_REASON = "retry_reason"
72
79
  TAG_EARLY_FLAKE_DETECTION_ABORT_REASON = "early_flake_detection_abort_reason"
73
80
  TAG_IS_NEW = "is_new"
74
81
  TAG_LIBRARY = "library"
@@ -79,6 +86,10 @@ module Datadog
79
86
  TAG_REQUEST_COMPRESSED = "rq_compressed"
80
87
  TAG_RESPONSE_COMPRESSED = "rs_compressed"
81
88
  TAG_COMMAND = "command"
89
+ TAG_IS_ATTEMPT_TO_FIX = "is_attempt_to_fix"
90
+ TAG_IS_QUARANTINED = "is_quarantined"
91
+ TAG_IS_TEST_DISABLED = "is_disabled"
92
+ TAG_HAS_FAILED_ALL_RETRIES = "has_failed_all_retries"
82
93
  # tags for git_requests.settings_response metric
83
94
  TAG_COVERAGE_ENABLED = "coverage_enabled"
84
95
  TAG_ITR_ENABLED = "itr_enabled"
@@ -86,6 +97,7 @@ module Datadog
86
97
  TAG_REQUIRE_GIT = "require_git"
87
98
  TAG_EARLY_FLAKE_DETECTION_ENABLED = "early_flake_detection_enabled"
88
99
  TAG_FLAKY_TEST_RETRIES_ENABLED = "flaky_test_retries_enabled"
100
+ TAG_KNOWN_TESTS_ENABLED = "known_tests_enabled"
89
101
  # tags for test_session metric
90
102
  TAG_PROVIDER = "provider"
91
103
  TAG_AUTO_INJECTED = "auto_injected"
@@ -58,21 +58,42 @@ module Datadog
58
58
  # version of the browser, if multiple browsers or multiple versions then this tag is empty
59
59
  TAG_BROWSER_VERSION = "test.browser.version"
60
60
 
61
+ # known and new tests
62
+ TAG_IS_NEW = "test.is_new" # true if test is new (it was not known to Datadog before)
63
+
61
64
  # Tags for retries
62
65
  TAG_IS_RETRY = "test.is_retry" # true if test was retried by datadog-ci library
63
- TAG_IS_NEW = "test.is_new" # true if test was marked as new by new test retries (early flake detection)
66
+ TAG_RETRY_REASON = "test.retry_reason" # reason why test was retried
64
67
  TAG_EARLY_FLAKE_ENABLED = "test.early_flake.enabled" # true if early flake detection is enabled
65
68
  TAG_EARLY_FLAKE_ABORT_REASON = "test.early_flake.abort_reason" # reason why early flake detection was aborted
66
69
 
67
70
  # Tags for total code coverage
68
71
  TAG_CODE_COVERAGE_LINES_PCT = "test.code_coverage.lines_pct"
69
72
 
73
+ # Tags for test managament
74
+ TAG_TEST_MANAGEMENT_ENABLED = "test.test_management.enabled" # true if test management is enabled, set on test_session_end event
75
+ TAG_IS_ATTEMPT_TO_FIX = "test.test_management.is_attempt_to_fix" # true if test is marked as "attempted to fix"
76
+ TAG_IS_TEST_DISABLED = "test.test_management.is_test_disabled" # true if test is marked as disabled in test management view
77
+ TAG_IS_QUARANTINED = "test.test_management.is_quarantined" # true if test is quarantined in test management view
78
+ TAG_HAS_FAILED_ALL_RETRIES = "test.has_failed_all_retries" # true if test was retried and none of the retries passed
79
+ TAG_ATTEMPT_TO_FIX_PASSED = "test.test_management.attempt_to_fix_passed" # true if test was marked as "attempted to fix" and all of the retries passed
80
+
81
+ # a set of tag indicating which capabilities (features) are supported by the library
82
+ module LibraryCapabilities
83
+ TAG_TEST_IMPACT_ANALYSIS = "_dd.library_capabilities.test_impact_analysis"
84
+ TAG_EARLY_FLAKE_DETECTION = "_dd.library_capabilities.early_flake_detection"
85
+ TAG_AUTO_TEST_RETRIES = "_dd.library_capabilities.auto_test_retries"
86
+ TAG_TEST_MANAGEMENT_QUARANTINE = "_dd.library_capabilities.test_management.quarantine"
87
+ TAG_TEST_MANAGEMENT_DISABLE = "_dd.library_capabilities.test_management.disable"
88
+ TAG_TEST_MANAGEMENT_ATTEMPT_TO_FIX = "_dd.library_capabilities.test_management.attempt_to_fix"
89
+ end
90
+
70
91
  # internal APM tag to mark a span as a test span
71
92
  TAG_SPAN_KIND = "span.kind"
72
93
  SPAN_KIND_TEST = "test"
73
94
 
74
- # common tags that are serialized directly in msgpack header in metadata field
75
- METADATA_TAG_TEST_SESSION_NAME = "test_session.name"
95
+ # DD_TEST_SESSION_NAME value
96
+ TAG_TEST_SESSION_NAME = "test_session.name"
76
97
 
77
98
  # internal tag indicating if datadog service was configured by the user
78
99
  TAG_USER_PROVIDED_TEST_SERVICE = "_dd.test.is_user_provided_service"
@@ -85,7 +106,6 @@ module Datadog
85
106
 
86
107
  # could be either "test" or "suite" depending on whether we skip individual tests or whole suites
87
108
  ITR_TEST_SKIPPING_MODE = "test" # we always skip tests (not suites) in Ruby
88
- ITR_TEST_SKIP_REASON = "Skipped by Datadog's intelligent test runner"
89
109
  ITR_UNSKIPPABLE_OPTION = :datadog_itr_unskippable
90
110
 
91
111
  EARLY_FLAKE_FAULTY = "faulty"
@@ -97,12 +117,30 @@ module Datadog
97
117
  SKIP = "skip"
98
118
  end
99
119
 
120
+ # test statuses that we use for execution stats but don't report to Datadog (e.g. fail_ignored)
121
+ module ExecutionStatsStatus
122
+ FAIL_IGNORED = "fail_ignored"
123
+ end
124
+
100
125
  # test types (e.g. test, benchmark, browser)
101
126
  module Type
102
127
  TEST = "test"
103
128
  BROWSER = "browser"
104
129
  BENCHMARK = "benchmark" # DEV: not used yet, will be used when benchmarks are supported
105
130
  end
131
+
132
+ # possible reasons why a test was retried
133
+ module RetryReason
134
+ RETRY_NEW = "efd"
135
+ RETRY_FAILED = "atr"
136
+ RETRY_FLAKY_FIXED = "attempt_to_fix"
137
+ end
138
+
139
+ # possible reasons why a test was skipped
140
+ module SkipReason
141
+ TEST_IMPACT_ANALYSIS = "Skipped by Datadog's Test Impact Analysis"
142
+ TEST_MANAGEMENT_DISABLED = "Flaky test is disabled by Datadog"
143
+ end
106
144
  end
107
145
  end
108
146
  end
@@ -38,10 +38,13 @@ module Datadog
38
38
  DD_API_SETTINGS_RESPONSE_TESTS_SKIPPING_KEY = "tests_skipping"
39
39
  DD_API_SETTINGS_RESPONSE_REQUIRE_GIT_KEY = "require_git"
40
40
  DD_API_SETTINGS_RESPONSE_FLAKY_TEST_RETRIES_KEY = "flaky_test_retries_enabled"
41
+ DD_API_SETTINGS_RESPONSE_KNOWN_TESTS_ENABLED_KEY = "known_tests_enabled"
41
42
  DD_API_SETTINGS_RESPONSE_EARLY_FLAKE_DETECTION_KEY = "early_flake_detection"
42
43
  DD_API_SETTINGS_RESPONSE_ENABLED_KEY = "enabled"
43
44
  DD_API_SETTINGS_RESPONSE_SLOW_TEST_RETRIES_KEY = "slow_test_retries"
44
45
  DD_API_SETTINGS_RESPONSE_FAULTY_SESSION_THRESHOLD_KEY = "faulty_session_threshold"
46
+ DD_API_SETTINGS_RESPONSE_TEST_MANAGEMENT_KEY = "test_management"
47
+ DD_API_SETTINGS_RESPONSE_ATTEMPT_TO_FIX_RETRIES_KEY = "attempt_to_fix_retries"
45
48
  DD_API_SETTINGS_RESPONSE_DEFAULT = {DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY => false}.freeze
46
49
 
47
50
  DD_API_GIT_SEARCH_COMMITS_PATH = "/api/v2/git/repository/search_commits"
@@ -54,6 +57,9 @@ module Datadog
54
57
  DD_API_UNIQUE_TESTS_PATH = "/api/v2/ci/libraries/tests"
55
58
  DD_API_UNIQUE_TESTS_TYPE = "ci_app_libraries_tests_request"
56
59
 
60
+ DD_API_TEST_MANAGEMENT_TESTS_PATH = "/api/v2/test/libraries/test-management/tests"
61
+ DD_API_TEST_MANAGEMENT_TESTS_TYPE = "ci_app_libraries_tests_request"
62
+
57
63
  CONTENT_TYPE_MESSAGEPACK = "application/msgpack"
58
64
  CONTENT_TYPE_JSON = "application/json"
59
65
  CONTENT_TYPE_MULTIPART_FORM_DATA = "multipart/form-data"
@@ -32,7 +32,9 @@ module Datadog
32
32
  # configure different components in parallel because they might block on HTTP requests
33
33
  configuration_workers = [
34
34
  Worker.new { test_optimisation.configure(library_configuration, test_session) },
35
- Worker.new { test_retries.configure(library_configuration, test_session) }
35
+ Worker.new { test_retries.configure(library_configuration, test_session) },
36
+ Worker.new { test_visibility.configure(library_configuration, test_session) },
37
+ Worker.new { test_management.configure(library_configuration, test_session) }
36
38
  ]
37
39
 
38
40
  # launch configuration workers
@@ -44,6 +46,14 @@ module Datadog
44
46
 
45
47
  private
46
48
 
49
+ def test_management
50
+ Datadog.send(:components).test_management
51
+ end
52
+
53
+ def test_visibility
54
+ Datadog.send(:components).test_visibility
55
+ end
56
+
47
57
  def test_optimisation
48
58
  Datadog.send(:components).test_optimisation
49
59
  end
@@ -98,6 +98,14 @@ module Datadog
98
98
  )
99
99
  end
100
100
 
101
+ def known_tests_enabled?
102
+ return @known_tests_enabled if defined?(@known_tests_enabled)
103
+
104
+ @known_tests_enabled = Utils::Parsing.convert_to_bool(
105
+ payload.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_KNOWN_TESTS_ENABLED_KEY, false)
106
+ )
107
+ end
108
+
101
109
  def slow_test_retries
102
110
  return @slow_test_retries if defined?(@slow_test_retries)
103
111
 
@@ -114,8 +122,31 @@ module Datadog
114
122
  )
115
123
  end
116
124
 
125
+ def test_management_enabled?
126
+ return @test_management_enabled if defined?(@test_management_enabled)
127
+
128
+ @test_management_enabled = Utils::Parsing.convert_to_bool(
129
+ test_management_payload.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_ENABLED_KEY, false)
130
+ )
131
+ end
132
+
133
+ def attempt_to_fix_retries_count
134
+ return @attempt_to_fix_retries_count if defined?(@attempt_to_fix_retries_count)
135
+
136
+ @attempt_to_fix_retries_count = test_management_payload.fetch(
137
+ Ext::Transport::DD_API_SETTINGS_RESPONSE_ATTEMPT_TO_FIX_RETRIES_KEY, nil
138
+ )
139
+ end
140
+
117
141
  private
118
142
 
143
+ def test_management_payload
144
+ payload.fetch(
145
+ Ext::Transport::DD_API_SETTINGS_RESPONSE_TEST_MANAGEMENT_KEY,
146
+ {}
147
+ )
148
+ end
149
+
119
150
  def early_flake_detection_payload
120
151
  payload.fetch(
121
152
  Ext::Transport::DD_API_SETTINGS_RESPONSE_EARLY_FLAKE_DETECTION_KEY,
@@ -62,7 +62,8 @@ module Datadog
62
62
  Ext::Telemetry::TAG_EARLY_FLAKE_DETECTION_ENABLED => library_settings.early_flake_detection_enabled?.to_s,
63
63
  Ext::Telemetry::TAG_FLAKY_TEST_RETRIES_ENABLED => library_settings.flaky_test_retries_enabled?.to_s,
64
64
  Ext::Telemetry::TAG_ITR_ENABLED => library_settings.itr_enabled?.to_s,
65
- Ext::Telemetry::TAG_REQUIRE_GIT => library_settings.require_git?.to_s
65
+ Ext::Telemetry::TAG_REQUIRE_GIT => library_settings.require_git?.to_s,
66
+ Ext::Telemetry::TAG_KNOWN_TESTS_ENABLED => library_settings.known_tests_enabled?.to_s
66
67
  }
67
68
  )
68
69
 
@@ -62,19 +62,37 @@ module Datadog
62
62
  get_tag(Ext::Test::TAG_TEST_SESSION_ID)
63
63
  end
64
64
 
65
- # Returns "true" if the test is skipped by the intelligent test runner.
66
- # @return [Boolean] true if the test is skipped by the intelligent test runner, false otherwise.
67
- def skipped_by_itr?
68
- get_tag(Ext::Test::TAG_ITR_SKIPPED_BY_ITR) == "true"
69
- end
70
-
71
65
  # Returns "true" if test span represents a retry.
72
66
  # @return [Boolean] true if this test is a retry, false otherwise.
73
67
  def is_retry?
74
68
  get_tag(Ext::Test::TAG_IS_RETRY) == "true"
75
69
  end
76
70
 
77
- # Marks this test as unskippable by the intelligent test runner.
71
+ # Returns "true" if this span represents a test that wasn't known to Datadog before.
72
+ # @return [Boolean] true if this test is a new one, false otherwise.
73
+ def is_new?
74
+ get_tag(Ext::Test::TAG_IS_NEW) == "true"
75
+ end
76
+
77
+ # Returns "true" if this test is quarantined by Datadog test management.
78
+ # @return [Boolean] true if this test is quarantined, false otherwise.
79
+ def quarantined?
80
+ get_tag(Ext::Test::TAG_IS_QUARANTINED) == "true"
81
+ end
82
+
83
+ # Returns "true" if this test is disabled by Datadog test management.
84
+ # @return [Boolean] true if this test is disabled, false otherwise.
85
+ def disabled?
86
+ get_tag(Ext::Test::TAG_IS_TEST_DISABLED) == "true"
87
+ end
88
+
89
+ # Returns "true" if this flaky test has fixing attempts (determined by Datadog backend).
90
+ # @return [Boolean] true if this test is attempted to be fixed.
91
+ def attempt_to_fix?
92
+ get_tag(Ext::Test::TAG_IS_ATTEMPT_TO_FIX) == "true"
93
+ end
94
+
95
+ # Marks this test as unskippable by the Test Impact Analysis.
78
96
  # This must be done before the test execution starts.
79
97
  #
80
98
  # Examples of tests that should be unskippable:
@@ -88,7 +106,7 @@ module Datadog
88
106
  TestOptimisation::Telemetry.itr_unskippable
89
107
  set_tag(Ext::Test::TAG_ITR_UNSKIPPABLE, "true")
90
108
 
91
- if skipped_by_itr?
109
+ if skipped_by_test_impact_analysis?
92
110
  clear_tag(Ext::Test::TAG_ITR_SKIPPED_BY_ITR)
93
111
 
94
112
  TestOptimisation::Telemetry.itr_forced_run
@@ -110,7 +128,13 @@ module Datadog
110
128
  def failed!(exception: nil)
111
129
  super
112
130
 
113
- record_test_result(Ext::Test::Status::FAIL)
131
+ # if we should ignore failures, we consider this test to be passed
132
+ if should_ignore_failures?
133
+ # use a special "fail_ignored" status to mark this test as failed but ignored
134
+ record_test_result(Ext::Test::ExecutionStatsStatus::FAIL_IGNORED)
135
+ else
136
+ record_test_result(Ext::Test::Status::FAIL)
137
+ end
114
138
  end
115
139
 
116
140
  # Sets the status of the span to "skip".
@@ -148,6 +172,40 @@ module Datadog
148
172
  !!test_suite&.any_test_retry_passed?(datadog_test_id)
149
173
  end
150
174
 
175
+ # @internal
176
+ def all_executions_failed?
177
+ !!test_suite&.all_executions_failed?(datadog_test_id)
178
+ end
179
+
180
+ # @internal
181
+ def all_executions_passed?
182
+ !!test_suite&.all_executions_passed?(datadog_test_id)
183
+ end
184
+
185
+ # @internal
186
+ def datadog_skip_reason
187
+ if skipped_by_test_impact_analysis?
188
+ Ext::Test::SkipReason::TEST_IMPACT_ANALYSIS
189
+ elsif disabled? || quarantined?
190
+ Ext::Test::SkipReason::TEST_MANAGEMENT_DISABLED
191
+ end
192
+ end
193
+
194
+ # @internal
195
+ def should_skip?
196
+ skipped_by_test_impact_analysis? || (disabled? && !attempt_to_fix?)
197
+ end
198
+
199
+ # @internal
200
+ def should_ignore_failures?
201
+ quarantined? || disabled? || any_retry_passed?
202
+ end
203
+
204
+ # @internal
205
+ def skipped_by_test_impact_analysis?
206
+ get_tag(Ext::Test::TAG_ITR_SKIPPED_BY_ITR) == "true"
207
+ end
208
+
151
209
  private
152
210
 
153
211
  def record_test_result(datadog_status)
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../ext/telemetry"
4
+ require_relative "../ext/test"
5
+ require_relative "../utils/telemetry"
6
+ require_relative "../utils/test_run"
7
+
8
+ module Datadog
9
+ module CI
10
+ module TestManagement
11
+ # Test management is a feature that lets people manage their flaky tests in Datadog.
12
+ # It includes:
13
+ # - marking test as quarantined causes test to continue running but not failing the build
14
+ # - marking test as disabled causes test to be skipped
15
+ # - marking test as "attempted to fix" causes test to be retried many times to confirm that fix worked
16
+ class Component
17
+ attr_reader :enabled, :tests_properties
18
+
19
+ def initialize(enabled:, tests_properties_client:)
20
+ @enabled = enabled
21
+
22
+ @tests_properties_client = tests_properties_client
23
+ @tests_properties = {}
24
+ end
25
+
26
+ def configure(library_settings, test_session)
27
+ @enabled &&= library_settings.test_management_enabled?
28
+
29
+ return unless @enabled
30
+
31
+ test_session.set_tag(Ext::Test::TAG_TEST_MANAGEMENT_ENABLED, "true")
32
+
33
+ @tests_properties = @tests_properties_client.fetch(test_session)
34
+
35
+ Utils::Telemetry.distribution(
36
+ Ext::Telemetry::METRIC_TEST_MANAGEMENT_TESTS_RESPONSE_TESTS,
37
+ @tests_properties.count.to_f
38
+ )
39
+ end
40
+
41
+ def tag_test_from_properties(test_span)
42
+ return unless @enabled
43
+
44
+ datadog_test_id = Utils::TestRun.datadog_test_id(test_span.name, test_span.test_suite_name)
45
+ test_properties = @tests_properties[datadog_test_id]
46
+
47
+ if test_properties.nil?
48
+ Datadog.logger.debug { "Test properties not found for test: #{datadog_test_id}" }
49
+ return
50
+ end
51
+
52
+ Datadog.logger.debug { "Test properties for test #{datadog_test_id} are: [#{test_properties}]" }
53
+
54
+ test_span.set_tag(Ext::Test::TAG_IS_QUARANTINED, "true") if test_properties["quarantined"]
55
+ test_span.set_tag(Ext::Test::TAG_IS_TEST_DISABLED, "true") if test_properties["disabled"]
56
+ test_span.set_tag(Ext::Test::TAG_IS_ATTEMPT_TO_FIX, "true") if test_properties["attempt_to_fix"]
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../ext/telemetry"
4
+ require_relative "../utils/telemetry"
5
+
6
+ module Datadog
7
+ module CI
8
+ module TestManagement
9
+ class NullComponent
10
+ attr_reader :enabled, :tests_properties
11
+
12
+ def initialize
13
+ @enabled = false
14
+ @tests_properties = {}
15
+ end
16
+
17
+ def configure(_, _)
18
+ end
19
+
20
+ def tag_test_from_properties(_)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ require_relative "../ext/telemetry"
6
+ require_relative "../ext/transport"
7
+ require_relative "../transport/telemetry"
8
+ require_relative "../utils/parsing"
9
+ require_relative "../utils/telemetry"
10
+ require_relative "../utils/test_run"
11
+
12
+ module Datadog
13
+ module CI
14
+ module TestManagement
15
+ # fetches and stores a map of tests to their test management properties from the backend
16
+ class TestsProperties
17
+ class Response
18
+ def initialize(http_response)
19
+ @http_response = http_response
20
+ @json = nil
21
+ end
22
+
23
+ def ok?
24
+ resp = @http_response
25
+ !resp.nil? && resp.ok?
26
+ end
27
+
28
+ def tests
29
+ tests_map = {}
30
+
31
+ payload
32
+ .fetch("data", {})
33
+ .fetch("attributes", {})
34
+ .fetch("modules", {})
35
+ .each do |_test_module, module_hash|
36
+ module_hash
37
+ .fetch("suites", {})
38
+ .each do |test_suite, suite_hash|
39
+ suite_hash.fetch("tests", {})
40
+ .each do |test_name, properties_hash|
41
+ properties = properties_hash.fetch("properties", {})
42
+ properties.transform_values! { |v| Utils::Parsing.convert_to_bool(v) }
43
+
44
+ tests_map[Utils::TestRun.datadog_test_id(test_name, test_suite)] = properties
45
+ end
46
+ end
47
+ end
48
+
49
+ tests_map
50
+ end
51
+
52
+ private
53
+
54
+ def payload
55
+ cached = @json
56
+ return cached unless cached.nil?
57
+
58
+ resp = @http_response
59
+ return @json = {} if resp.nil? || !ok?
60
+
61
+ begin
62
+ @json = JSON.parse(resp.payload)
63
+ rescue JSON::ParserError => e
64
+ Datadog.logger.error(
65
+ "Failed to parse test management tests response payload: #{e}. Payload was: #{resp.payload}"
66
+ )
67
+ @json = {}
68
+ end
69
+ end
70
+ end
71
+
72
+ def initialize(api: nil)
73
+ @api = api
74
+ end
75
+
76
+ def fetch(test_session)
77
+ api = @api
78
+ return {} unless api
79
+
80
+ request_payload = payload(test_session)
81
+ Datadog.logger.debug("Fetching test management tests with request: #{request_payload}")
82
+
83
+ http_response = api.api_request(
84
+ path: Ext::Transport::DD_API_TEST_MANAGEMENT_TESTS_PATH,
85
+ payload: request_payload
86
+ )
87
+
88
+ CI::Transport::Telemetry.api_requests(
89
+ Ext::Telemetry::METRIC_TEST_MANAGEMENT_TESTS_REQUEST,
90
+ 1,
91
+ compressed: http_response.request_compressed
92
+ )
93
+ Utils::Telemetry.distribution(Ext::Telemetry::METRIC_TEST_MANAGEMENT_TESTS_REQUEST_MS, http_response.duration_ms)
94
+ Utils::Telemetry.distribution(
95
+ Ext::Telemetry::METRIC_TEST_MANAGEMENT_TESTS_RESPONSE_BYTES,
96
+ http_response.response_size.to_f,
97
+ {Ext::Telemetry::TAG_RESPONSE_COMPRESSED => http_response.gzipped_content?.to_s}
98
+ )
99
+
100
+ unless http_response.ok?
101
+ CI::Transport::Telemetry.api_requests_errors(
102
+ Ext::Telemetry::METRIC_TEST_MANAGEMENT_TESTS_REQUEST_ERRORS,
103
+ 1,
104
+ error_type: http_response.telemetry_error_type,
105
+ status_code: http_response.code
106
+ )
107
+ end
108
+
109
+ Response.new(http_response).tests
110
+ end
111
+
112
+ private
113
+
114
+ def payload(test_session)
115
+ {
116
+ "data" => {
117
+ "id" => Datadog::Core::Environment::Identity.id,
118
+ "type" => Ext::Transport::DD_API_TEST_MANAGEMENT_TESTS_TYPE,
119
+ "attributes" => {
120
+ "repository_url" => test_session.git_repository_url
121
+ }
122
+ }
123
+ }.to_json
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "pp"
4
4
 
5
+ require "datadog/core/telemetry/logging"
5
6
  require "datadog/core/utils/forking"
6
7
 
7
8
  require_relative "../ext/test"
@@ -19,7 +20,7 @@ require_relative "telemetry"
19
20
  module Datadog
20
21
  module CI
21
22
  module TestOptimisation
22
- # Intelligent test runner implementation
23
+ # Test Impact Analysis implementation
23
24
  # Integrates with backend to provide test impact analysis data and
24
25
  # skip tests that are not impacted by the changes
25
26
  class Component
@@ -155,7 +156,7 @@ module Datadog
155
156
 
156
157
  if skippable?(test)
157
158
  if forked?
158
- Datadog.logger.warn { "Intelligent test runner is not supported for forking test runners yet" }
159
+ Datadog.logger.warn { "Test Impact Analysis is not supported for forking test runners yet" }
159
160
  return
160
161
  end
161
162
 
@@ -171,7 +172,7 @@ module Datadog
171
172
  @mutex.synchronize do
172
173
  @total_tests_count += 1
173
174
 
174
- return if !test.skipped? || !test.skipped_by_itr?
175
+ return if !test.skipped? || !test.skipped_by_test_impact_analysis?
175
176
 
176
177
  if forked?
177
178
  Datadog.logger.warn { "ITR is not supported for forking test runners yet" }
@@ -224,6 +225,7 @@ module Datadog
224
225
  Datadog.logger.debug("Loaded Datadog code coverage collector, using coverage mode: #{code_coverage_mode}")
225
226
  rescue LoadError => e
226
227
  Datadog.logger.error("Failed to load coverage collector: #{e}. Code coverage will not be collected.")
228
+ Core::Telemetry::Logger.report(e, description: "Failed to load coverage collector")
227
229
 
228
230
  @code_coverage_enabled = false
229
231
  end