datadog-ci 1.23.3 → 1.24.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a911ce98b1577d684374a00e8efd9ecbcf34fac28f24bb543411dbb762163d8c
4
- data.tar.gz: 93c277bf2fc40ccdede20e48102eb6b1c1721336d80b62e60c9a191b807f8754
3
+ metadata.gz: 30bc86afa5d05d656f9abfa7ffb5ff21d6f9a8fc5edeb5924651de4cdc6df10d
4
+ data.tar.gz: c846a97a47a74120d9926350360cefa4ca3fcdadea6819337ad2990f7856e267
5
5
  SHA512:
6
- metadata.gz: eb8fef68d0b4e9e6dc02aa76c603b98e854b09ef47913c30c986d4e397e9b48c79ec14d5ebb9dd1e48db0737def7d73f7e33aefbb35039441e65b4005884051a
7
- data.tar.gz: 072aa07eb4af30102c588a54c75398634e1b409cd1698a087b8e0cb030bc6bb6c5bd8b98ba2c57716e8d819b61ace6ed9ed4ea39a5eda0e1accedc0e232a1eaf
6
+ metadata.gz: 2e2a5b3efa12f1c64176e1751e86ebf803f8c894aba7c53453abb22732e0ed16a6087292538b2e03f0657dc9433ceba9e452f5899a95ace325a004ee12c37fbc
7
+ data.tar.gz: 461912b9cb9f406c4fd1cb9975ece506df7e979ed7b373b46c9e0060977f397b2da63095be353351d74b4273ea3cc1a354c12a4ef9dc7bf75a0a5ed065a16fa3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.24.0] - 2025-12-15
4
+
5
+ ### Added
6
+ * Create final status tag on test events ([#433][])
7
+
8
+ ### Changed
9
+ * Add `branch` parameter to `/test-management/tests` request ([#436][])
10
+
11
+ ### Fixed
12
+ * Fix an inheritance blind spot for Test Impact Analysis ([#440][])
13
+
3
14
  ## [1.23.3] - 2025-11-19
4
15
 
5
16
  ### Fixed
@@ -554,7 +565,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil
554
565
 
555
566
  - Ruby versions < 2.7 no longer supported ([#8][])
556
567
 
557
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.3...main
568
+ [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.24.0...main
569
+ [1.24.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.3...v1.24.0
558
570
  [1.23.3]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.2...v1.23.3
559
571
  [1.23.2]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.1...v1.23.2
560
572
  [1.23.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.0...v1.23.1
@@ -786,4 +798,7 @@ Currently test suite level visibility is not used by our instrumentation: it wil
786
798
  [#416]: https://github.com/DataDog/datadog-ci-rb/issues/416
787
799
  [#418]: https://github.com/DataDog/datadog-ci-rb/issues/418
788
800
  [#425]: https://github.com/DataDog/datadog-ci-rb/issues/425
789
- [#430]: https://github.com/DataDog/datadog-ci-rb/issues/430
801
+ [#430]: https://github.com/DataDog/datadog-ci-rb/issues/430
802
+ [#433]: https://github.com/DataDog/datadog-ci-rb/issues/433
803
+ [#436]: https://github.com/DataDog/datadog-ci-rb/issues/436
804
+ [#440]: https://github.com/DataDog/datadog-ci-rb/issues/440
@@ -156,12 +156,12 @@ static VALUE dd_cov_allocate(VALUE klass) {
156
156
 
157
157
  // Checks if the filename is located under the root folder of the project (but
158
158
  // not in the ignored folder) and adds it to the impacted_files hash.
159
- static void record_impacted_file(struct dd_cov_data *dd_cov_data,
159
+ static bool record_impacted_file(struct dd_cov_data *dd_cov_data,
160
160
  VALUE filename) {
161
161
  char *filename_ptr = RSTRING_PTR(filename);
162
162
  // if the current filename is not located under the root, we skip it
163
163
  if (strncmp(dd_cov_data->root, filename_ptr, dd_cov_data->root_len) != 0) {
164
- return;
164
+ return false;
165
165
  }
166
166
 
167
167
  // if ignored_path is provided and the current filename is located under the
@@ -170,10 +170,11 @@ static void record_impacted_file(struct dd_cov_data *dd_cov_data,
170
170
  if (dd_cov_data->ignored_path_len != 0 &&
171
171
  strncmp(dd_cov_data->ignored_path, filename_ptr,
172
172
  dd_cov_data->ignored_path_len) == 0) {
173
- return;
173
+ return false;
174
174
  }
175
175
 
176
176
  rb_hash_aset(dd_cov_data->impacted_files, filename, Qtrue);
177
+ return true;
177
178
  }
178
179
 
179
180
  // Executed on RUBY_EVENT_LINE event and captures the filename from
@@ -221,29 +222,61 @@ static VALUE safely_get_source_location(VALUE klass_name) {
221
222
  return rescue_nil(get_source_location, klass_name);
222
223
  }
223
224
 
224
- // This function is called for each class that was instantiated during the test
225
- // run.
226
- static int process_instantiated_klass(st_data_t key, st_data_t _value,
227
- st_data_t data) {
228
- VALUE klass = (VALUE)key;
229
- struct dd_cov_data *dd_cov_data = (struct dd_cov_data *)data;
225
+ // Safely get class name, returns Qnil on any error
226
+ static VALUE safely_get_class_name(VALUE klass) {
227
+ return rescue_nil(rb_class_name, klass);
228
+ }
230
229
 
231
- VALUE klass_name = rb_class_name(klass);
230
+ // Safely get module ancestors, returns Qnil on any error
231
+ static VALUE safely_get_mod_ancestors(VALUE klass) {
232
+ return rescue_nil(rb_mod_ancestors, klass);
233
+ }
234
+
235
+ static bool record_impacted_klass(struct dd_cov_data *dd_cov_data,
236
+ VALUE klass) {
237
+ VALUE klass_name = safely_get_class_name(klass);
232
238
  if (klass_name == Qnil) {
233
- return ST_CONTINUE;
239
+ return false;
234
240
  }
235
241
 
236
242
  VALUE source_location = safely_get_source_location(klass_name);
237
- if (source_location == Qnil || RARRAY_LEN(source_location) == 0) {
238
- return ST_CONTINUE;
243
+ if (source_location == Qnil || !RB_TYPE_P(source_location, T_ARRAY) ||
244
+ RARRAY_LEN(source_location) == 0) {
245
+ return false;
239
246
  }
240
247
 
241
248
  VALUE filename = RARRAY_AREF(source_location, 0);
242
249
  if (filename == Qnil || !RB_TYPE_P(filename, T_STRING)) {
250
+ return false;
251
+ }
252
+
253
+ return record_impacted_file(dd_cov_data, filename);
254
+ }
255
+
256
+ // This function is called for each class that was instantiated during the test
257
+ // run.
258
+ static int each_instantiated_klass(st_data_t key, st_data_t _value,
259
+ st_data_t data) {
260
+ VALUE klass = (VALUE)key;
261
+ struct dd_cov_data *dd_cov_data = (struct dd_cov_data *)data;
262
+
263
+ // rb_mod_ancestors returns an array containing the "klass" itself
264
+ // and all the parent classes and/or included/prepended modules
265
+ VALUE ancestors = safely_get_mod_ancestors(klass);
266
+ if (ancestors == Qnil || !RB_TYPE_P(ancestors, T_ARRAY)) {
243
267
  return ST_CONTINUE;
244
268
  }
245
269
 
246
- record_impacted_file(dd_cov_data, filename);
270
+ long len = RARRAY_LEN(ancestors);
271
+ for (long i = 0; i < len; i++) {
272
+ VALUE mod = rb_ary_entry(ancestors, i);
273
+ if (mod == Qnil) {
274
+ continue;
275
+ }
276
+
277
+ record_impacted_klass(dd_cov_data, mod);
278
+ }
279
+
247
280
  return ST_CONTINUE;
248
281
  }
249
282
 
@@ -385,7 +418,7 @@ static VALUE dd_cov_stop(VALUE self) {
385
418
  }
386
419
 
387
420
  // process classes covered by allocation tracing
388
- st_foreach(dd_cov_data->klasses_table, process_instantiated_klass,
421
+ st_foreach(dd_cov_data->klasses_table, each_instantiated_klass,
389
422
  (st_data_t)dd_cov_data);
390
423
  st_clear(dd_cov_data->klasses_table);
391
424
 
@@ -13,7 +13,21 @@ module Datadog
13
13
  TAG_FRAMEWORK_VERSION = "test.framework_version"
14
14
  TAG_NAME = "test.name"
15
15
  TAG_SKIP_REASON = "test.skip_reason"
16
+
17
+ # Status is the result of a single test run
18
+ # See the [Datadog::CI::Ext::Test::Status] module for the list of possible values of this tag
16
19
  TAG_STATUS = "test.status"
20
+ # Final status is the result that Datadog reports after all retries for a given test. It might be different
21
+ # from the status of the given test run:
22
+ #
23
+ # Example: new test was retried 10 times by Early Flake Detection. It succeeded 9 times and failed once.
24
+ # The final status will be "pass" because we keep CI green for flaky tests.
25
+ #
26
+ # This tag is useful to create monitors on hard failures: if test.final_status is "fail", then CI is red.
27
+ #
28
+ # See the [Datadog::CI::Ext::Test::Status] module for the list of possible values of this tag
29
+ TAG_FINAL_STATUS = "test.final_status"
30
+
17
31
  TAG_SUITE = "test.suite"
18
32
  TAG_MODULE = "test.module"
19
33
  TAG_TYPE = "test.type"
@@ -244,6 +244,32 @@ module Datadog
244
244
  get_tag(Ext::Test::TAG_ITR_SKIPPED_BY_ITR) == "true"
245
245
  end
246
246
 
247
+ # @internal
248
+ def record_final_status
249
+ status = get_tag(Ext::Test::TAG_STATUS)
250
+ return if status.nil?
251
+
252
+ if [Ext::Test::Status::PASS, Ext::Test::Status::SKIP].include?(status)
253
+ set_tag(Ext::Test::TAG_FINAL_STATUS, status)
254
+ return
255
+ end
256
+
257
+ if should_ignore_failures?
258
+ set_tag(Ext::Test::TAG_FINAL_STATUS, Ext::Test::Status::PASS)
259
+ else
260
+ set_tag(Ext::Test::TAG_FINAL_STATUS, Ext::Test::Status::FAIL)
261
+ end
262
+ end
263
+
264
+ # @internal
265
+ def peek_duration
266
+ end_time = Core::Utils::Time.now.utc
267
+ start_time = tracer_span.start_time
268
+
269
+ return 0.0 if start_time.nil? || end_time.nil?
270
+ end_time - start_time
271
+ end
272
+
247
273
  private
248
274
 
249
275
  def record_test_result(datadog_status)
@@ -127,7 +127,8 @@ module Datadog
127
127
  "attributes" => {
128
128
  "repository_url" => test_session.git_repository_url,
129
129
  "commit_message" => test_session.original_git_commit_message,
130
- "sha" => test_session.original_git_commit_sha
130
+ "sha" => test_session.original_git_commit_sha,
131
+ "branch" => test_session.git_branch
131
132
  }
132
133
  }
133
134
  }.to_json
@@ -94,18 +94,27 @@ module Datadog
94
94
 
95
95
  def record_test_finished(test_span)
96
96
  if current_retry_driver.nil?
97
- # we always run test at least once and after the first pass create a correct retry driver
97
+ # We always run test at least once and after the first pass create a correct retry driver
98
98
  self.current_retry_driver = build_driver(test_span)
99
99
  else
100
- # after each retry we record the result, the driver will decide if we should retry again
100
+ # After each retry we let the driver to record the result.
101
+ # Then the driver will decide if we should retry again.
101
102
  current_retry_driver&.record_retry(test_span)
102
103
 
104
+ # We know that the test was already retried at least once so if we should not retry anymore, then this
105
+ # is the last retry.
103
106
  tag_last_retry(test_span) unless should_retry?
104
107
  end
105
- end
106
108
 
107
- def record_test_span_duration(tracer_span)
108
- current_retry_driver&.record_duration(tracer_span.duration)
109
+ # Some retry strategies such as Early Flake Detection change the number of retries based on
110
+ # how long the test was.
111
+ current_retry_driver&.record_duration(test_span.peek_duration)
112
+
113
+ # We need to set the final status of the test (what will be reported to the test framework) on the last execution
114
+ # no matter if test was retried or not
115
+ #
116
+ # If we should not retry at this point, it means that this execution is the last one (it might the only one as well).
117
+ test_span.record_final_status unless should_retry?
109
118
  end
110
119
 
111
120
  # this API is targeted on Cucumber instrumentation or any other that cannot leverage #with_retries method
@@ -121,8 +121,6 @@ module Datadog
121
121
 
122
122
  if block
123
123
  @context.trace_test(test_name, test_suite, service: service, tags: tags) do |test|
124
- subscribe_to_after_stop_event(test.tracer_span)
125
-
126
124
  on_test_started(test)
127
125
  res = block.call(test)
128
126
  on_test_finished(test)
@@ -130,7 +128,6 @@ module Datadog
130
128
  end
131
129
  else
132
130
  test = @context.trace_test(test_name, test_suite, service: service, tags: tags)
133
- subscribe_to_after_stop_event(test.tracer_span)
134
131
  on_test_started(test)
135
132
  test
136
133
  end
@@ -340,10 +337,6 @@ module Datadog
340
337
  Telemetry.event_finished(test)
341
338
  end
342
339
 
343
- def on_after_test_span_finished(tracer_span)
344
- test_retries.record_test_span_duration(tracer_span)
345
- end
346
-
347
340
  # HELPERS
348
341
  def single_active_test_suite
349
342
  # when fetching test_suite to use as test's context, try local context instance first
@@ -357,14 +350,6 @@ module Datadog
357
350
  block&.call(nil)
358
351
  end
359
352
 
360
- def subscribe_to_after_stop_event(tracer_span)
361
- events = tracer_span.send(:events)
362
-
363
- events.after_stop.subscribe do |span|
364
- on_after_test_span_finished(span)
365
- end
366
- end
367
-
368
353
  def set_codeowners(span)
369
354
  source = span.source_file
370
355
  owners = @codeowners.list_owners(source) if source
@@ -4,8 +4,8 @@ module Datadog
4
4
  module CI
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 23
8
- PATCH = 3
7
+ MINOR = 24
8
+ PATCH = 0
9
9
  PRE = nil
10
10
  BUILD = nil
11
11
  # PRE and BUILD above are modified for dev gems during gem build GHA workflow
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog-ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.23.3
4
+ version: 1.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.