datadog-ci 1.37.0 → 1.38.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: 2a4aa364e13169f17cd0c37cc4dff152b1f8861895569d7d5bdf0a9398d2415f
4
- data.tar.gz: 900cc26e588995ffe4dde4c32a42aee348593adf521a498d82d138233595861b
3
+ metadata.gz: 4a6bdfb71fe5140388ad8b1ea7a086977ba9a4a69c86fdc1b465746f287ba2ec
4
+ data.tar.gz: 27615bb724035bc22617b7495cac97cac343fac08da9363fba31e7a4fc21a84e
5
5
  SHA512:
6
- metadata.gz: f9a3e6033dd78d3570da5184f1e220abdf7c3aeb7a9ed76cd1ff25440d906ea0f2a20670930270677c4e17826e6a7283ad411a71cdd446c01b16340dc5c86cce
7
- data.tar.gz: 5e5eb24079c51dc28c78a22c07e185e6501917ef9dcb448b812cb76dc5243efe84fbe132042b1049bfc626c93ae057e8db954cad48c9f8a40e93dafdddfb3daf
6
+ metadata.gz: 9ae087aef544b7775bb9784fb64a27a836e3181076229cac5c814d4263ecb02432f723d9e136350aba020d6ad67820a8658edcce62fb3dcccea5d5efff999820
7
+ data.tar.gz: aca348e24dba29c5a98fe60fe7e89879257e1466bce6b179c9efd43c40e4f3f1cffb2641f1d35fd54adbd111ba0c1f957db144848702fb51b0bf567282508ace
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.38.0] - 2026-08-28
4
+
5
+ ### Added
6
+ * Trace RSpec and Minitest lifecycle steps ([#595][])
7
+
8
+ ### Changed
9
+ * Optimize TIA performance ([#589][])
10
+
11
+ ### Fixed
12
+ * Isolate state between parallel_tests sessions ([#593][])
13
+ * Test Optimization telemetry shutdown order ([#586][])
14
+
15
+ ### Removed
16
+ * Disable LiveDebugger's SymDB upload in CI mode ([#594][])
17
+
3
18
  ## [1.37.0] - 2026-08-19
4
19
 
5
20
  ## [1.36.1] - 2026-08-11
@@ -684,7 +699,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil
684
699
 
685
700
  - Ruby versions < 2.7 no longer supported ([#8][])
686
701
 
687
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.37.0...main
702
+ [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.38.0...main
703
+ [1.38.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.37.0...v1.38.0
688
704
  [1.37.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.36.1...v1.37.0
689
705
  [1.36.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.36.0...v1.36.1
690
706
  [1.36.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.35.0...v1.36.0
@@ -969,4 +985,9 @@ Currently test suite level visibility is not used by our instrumentation: it wil
969
985
  [#554]: https://github.com/DataDog/datadog-ci-rb/issues/554
970
986
  [#555]: https://github.com/DataDog/datadog-ci-rb/issues/555
971
987
  [#566]: https://github.com/DataDog/datadog-ci-rb/issues/566
972
- [#570]: https://github.com/DataDog/datadog-ci-rb/issues/570
988
+ [#570]: https://github.com/DataDog/datadog-ci-rb/issues/570
989
+ [#586]: https://github.com/DataDog/datadog-ci-rb/issues/586
990
+ [#589]: https://github.com/DataDog/datadog-ci-rb/issues/589
991
+ [#593]: https://github.com/DataDog/datadog-ci-rb/issues/593
992
+ [#594]: https://github.com/DataDog/datadog-ci-rb/issues/594
993
+ [#595]: https://github.com/DataDog/datadog-ci-rb/issues/595
@@ -13,11 +13,9 @@
13
13
 
14
14
  struct packed_files_context {
15
15
  VALUE root;
16
- VALUE seen;
17
16
  VALUE packed;
18
17
  long root_len;
19
18
  uint32_t files_count;
20
- bool direct_absolute;
21
19
  bool fast_path_supported;
22
20
  };
23
21
 
@@ -92,8 +90,6 @@ static bool packed_files_append_entry(struct packed_files_context *context,
92
90
  return false;
93
91
  }
94
92
 
95
- VALUE relative_file = file;
96
- VALUE dedup_file = file;
97
93
  long relative_len = RSTRING_LEN(file);
98
94
  long relative_offset = 0;
99
95
  const char *file_ptr = RSTRING_PTR(file);
@@ -117,15 +113,11 @@ static bool packed_files_append_entry(struct packed_files_context *context,
117
113
  if (relative_len == 0) {
118
114
  return true;
119
115
  }
120
- if (context->direct_absolute) {
121
- relative_offset = context->root_len + 1;
122
- } else {
123
- relative_file = rb_str_substr(file, context->root_len + 1, file_len);
124
- dedup_file = relative_file;
125
- }
116
+ relative_offset = context->root_len + 1;
126
117
  } else if (!additional_file) {
127
- // Relative primary paths depend on cwd-to-root Pathname semantics. They
128
- // are uncommon and retain the authoritative Ruby fallback.
118
+ // Production DDCov and static-dependency extraction only emit absolute
119
+ // paths after filtering them against the repository root. Keep the Ruby
120
+ // fallback for direct callers that violate this invariant.
129
121
  context->fast_path_supported = false;
130
122
  return false;
131
123
  }
@@ -133,17 +125,12 @@ static bool packed_files_append_entry(struct packed_files_context *context,
133
125
  if (relative_len == 0) {
134
126
  return true;
135
127
  }
136
- if (rb_hash_lookup2(context->seen, dedup_file, Qundef) != Qundef) {
137
- return true;
138
- }
139
- rb_hash_aset(context->seen, dedup_file, Qtrue);
140
128
 
141
- int encoding_index = rb_enc_get_index(relative_file);
129
+ int encoding_index = rb_enc_get_index(file);
142
130
  bool binary = encoding_index == rb_ascii8bit_encindex();
143
131
  if (!binary && encoding_index != rb_utf8_encindex() &&
144
132
  encoding_index != rb_usascii_encindex() &&
145
- (!rb_enc_str_asciicompat_p(relative_file) ||
146
- !string_bytes_are_ascii(relative_file))) {
133
+ (!rb_enc_str_asciicompat_p(file) || !string_bytes_are_ascii(file))) {
147
134
  context->fast_path_supported = false;
148
135
  return false;
149
136
  }
@@ -159,9 +146,8 @@ static bool packed_files_append_entry(struct packed_files_context *context,
159
146
  sizeof(filename_entry_prefix));
160
147
  packed_files_append_string_header(context->packed, (uint32_t)relative_len,
161
148
  binary);
162
- const char *relative_ptr = RSTRING_PTR(relative_file) + relative_offset;
149
+ const char *relative_ptr = RSTRING_PTR(file) + relative_offset;
163
150
  rb_str_cat(context->packed, relative_ptr, relative_len);
164
- RB_GC_GUARD(relative_file);
165
151
  context->files_count++;
166
152
  return true;
167
153
  }
@@ -174,24 +160,6 @@ static int pack_primary_file_i(VALUE file, VALUE _value,
174
160
  : ST_STOP;
175
161
  }
176
162
 
177
- static bool packed_file_is_absolute(VALUE file, bool additional_file) {
178
- if (file == Qnil && !additional_file) {
179
- return true;
180
- }
181
- return RB_TYPE_P(file, T_STRING) && rb_obj_class(file) == rb_cString &&
182
- RSTRING_LEN(file) > 0 && RSTRING_PTR(file)[0] == '/';
183
- }
184
-
185
- static int detect_absolute_primary_file_i(VALUE file, VALUE _value,
186
- VALUE all_absolute_value) {
187
- bool *all_absolute = (bool *)all_absolute_value;
188
- if (!packed_file_is_absolute(file, false)) {
189
- *all_absolute = false;
190
- return ST_STOP;
191
- }
192
- return ST_CONTINUE;
193
- }
194
-
195
163
  static void
196
164
  packed_files_write_array_header(struct packed_files_context *context) {
197
165
  unsigned char header[5];
@@ -233,22 +201,13 @@ static VALUE file_serialization_pack_files(VALUE module, VALUE primary_files,
233
201
  return Qnil;
234
202
  }
235
203
 
236
- bool all_absolute = true;
237
- rb_hash_foreach(primary_files, detect_absolute_primary_file_i,
238
- (VALUE)&all_absolute);
239
204
  long additional_files_len = RARRAY_LEN(additional_files);
240
- for (long i = 0; all_absolute && i < additional_files_len; i++) {
241
- all_absolute =
242
- packed_file_is_absolute(rb_ary_entry(additional_files, i), true);
243
- }
244
205
 
245
206
  struct packed_files_context context = {
246
207
  .root = root,
247
- .seen = rb_hash_new(),
248
208
  .packed = rb_str_buf_new(4096),
249
209
  .root_len = RSTRING_LEN(root),
250
210
  .files_count = 0,
251
- .direct_absolute = all_absolute,
252
211
  .fast_path_supported = true};
253
212
  rb_str_resize(context.packed, 5);
254
213
 
@@ -72,8 +72,6 @@ module Datadog
72
72
  end
73
73
 
74
74
  def shutdown!(replacement = nil)
75
- super
76
-
77
75
  @test_optimization_cache&.shutdown!
78
76
  @test_tracing&.shutdown!
79
77
  @test_impact_analysis&.shutdown!
@@ -81,9 +79,14 @@ module Datadog
81
79
  @test_discovery&.shutdown!
82
80
  @code_coverage&.shutdown!
83
81
  @git_tree_upload_worker&.stop
82
+ ensure
83
+ super
84
84
  end
85
85
 
86
86
  def activate_ci!(settings)
87
+ # Symbol Database upload is not supported in CI mode. Older datadog versions do not expose this setting.
88
+ settings.symbol_database.enabled = false if settings.respond_to?(:symbol_database)
89
+
87
90
  unless settings.tracing.enabled
88
91
  Datadog.logger.error(
89
92
  "Test Optimization requires tracing to be enabled. Disabling Test Optimization. " \
@@ -12,6 +12,10 @@ module Datadog
12
12
  FRAMEWORK = "minitest"
13
13
 
14
14
  DEFAULT_SERVICE_NAME = "minitest"
15
+
16
+ STEP_SPAN_TYPE = "step"
17
+ BEFORE_STEP_SPAN_NAME = "before"
18
+ AFTER_STEP_SPAN_NAME = "after"
15
19
  end
16
20
  end
17
21
  end
@@ -48,18 +48,57 @@ module Datadog
48
48
  super
49
49
  end
50
50
 
51
- def after_teardown
51
+ def before_setup
52
52
  test_span = _dd_test_tracing_component.active_test
53
53
  return super unless test_span
54
54
 
55
- finish_with_result(test_span, result_code)
55
+ @datadog_before_step_failures_count = failures.length
56
+ @datadog_before_step_span = _dd_test_tracing_component.trace(
57
+ Ext::BEFORE_STEP_SPAN_NAME,
58
+ type: Ext::STEP_SPAN_TYPE
59
+ )
60
+
61
+ super
62
+ end
63
+
64
+ def after_setup
65
+ super
66
+ ensure
67
+ finish_datadog_before_step
68
+ end
56
69
 
57
- # remove failures if failure can be ignored because of retries
58
- self.failures = [] if test_span.should_ignore_failures?
70
+ def before_teardown
71
+ test_span = _dd_test_tracing_component.active_test
72
+ return super unless test_span
73
+
74
+ # Setup failures prevent Minitest from invoking after_setup, so
75
+ # close a still-active before span before teardown begins.
76
+ finish_datadog_before_step
77
+
78
+ @datadog_after_step_failures_count = failures.length
79
+ @datadog_after_step_span = _dd_test_tracing_component.trace(
80
+ Ext::AFTER_STEP_SPAN_NAME,
81
+ type: Ext::STEP_SPAN_TYPE
82
+ )
59
83
 
60
84
  super
61
85
  end
62
86
 
87
+ def after_teardown
88
+ test_span = _dd_test_tracing_component.active_test
89
+ return super unless test_span
90
+
91
+ begin
92
+ super
93
+ ensure
94
+ finish_datadog_after_step
95
+ finish_with_result(test_span, result_code)
96
+
97
+ # remove failures if failure can be ignored because of retries
98
+ self.failures = [] if test_span.should_ignore_failures?
99
+ end
100
+ end
101
+
63
102
  private
64
103
 
65
104
  def datadog_run_reentered?
@@ -115,6 +154,32 @@ module Datadog
115
154
  Helpers.start_test_suite(self.class)
116
155
  end
117
156
 
157
+ def finish_datadog_before_step
158
+ span = @datadog_before_step_span
159
+ return unless span
160
+
161
+ finish_datadog_step(span, @datadog_before_step_failures_count)
162
+ @datadog_before_step_span = nil
163
+ end
164
+
165
+ def finish_datadog_after_step
166
+ span = @datadog_after_step_span
167
+ return unless span
168
+
169
+ finish_datadog_step(span, @datadog_after_step_failures_count)
170
+ @datadog_after_step_span = nil
171
+ end
172
+
173
+ def finish_datadog_step(span, failures_count)
174
+ step_failure = failures[failures_count]
175
+ if step_failure && !step_failure.is_a?(::Minitest::Skip)
176
+ exception = step_failure.respond_to?(:error) ? step_failure.error : step_failure
177
+ span.failed!(exception: exception)
178
+ end
179
+
180
+ span.finish
181
+ end
182
+
118
183
  def skip_datadog_test(test_span)
119
184
  time_it do
120
185
  capture_exceptions do
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../../ext/test"
4
+ require_relative "../../utils/file_storage"
4
5
  require_relative "../rspec/ext"
5
6
 
6
7
  module Datadog
@@ -17,7 +18,7 @@ module Datadog
17
18
  # only rspec runner is supported for now
18
19
  return super if @runner != ::ParallelTests::RSpec::Runner
19
20
 
20
- begin
21
+ Utils::FileStorage.with_new_namespace do |storage_namespace|
21
22
  # Preserve activation explicitly for the RSpec worker commands.
22
23
  # Starting the distributed parent session removes inherited
23
24
  # activation before unrelated child processes can receive it.
@@ -35,6 +36,7 @@ module Datadog
35
36
 
36
37
  options[:env] ||= {}
37
38
  options[:env][CI::Ext::Settings::ENV_TEST_VISIBILITY_DRB_SERVER_URI] = test_tracing_component.context_service_uri
39
+ options[:env][Utils::FileStorage::ENV_NAMESPACE] = storage_namespace
38
40
  options[:env]["RUBYOPT"] ||= worker_rubyopt if worker_rubyopt
39
41
 
40
42
  super
@@ -26,8 +26,9 @@ module Datadog
26
26
  # ============================================
27
27
 
28
28
  def run(*args)
29
- return super unless datadog_configuration[:enabled]
30
- return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled]
29
+ return super unless datadog_tracing_enabled?
30
+
31
+ @datadog_test_tracing_component = test_tracing_component
31
32
 
32
33
  test_suite_span = test_tracing_component.start_test_suite(datadog_test_suite_name) if ci_queue?
33
34
 
@@ -76,6 +77,8 @@ module Datadog
76
77
  # after retries are done, we must report the test to RSpec
77
78
  @skip_reporting = false
78
79
  finish(reporter)
80
+ ensure
81
+ @datadog_test_tracing_component = nil
79
82
  end
80
83
 
81
84
  def finish(reporter)
@@ -193,6 +196,40 @@ module Datadog
193
196
  # Run method helpers
194
197
  # ============================================
195
198
 
199
+ def run_before_example
200
+ return super unless @datadog_test_tracing_component
201
+
202
+ skip_exception = nil
203
+ # @type var skip_exception: ::RSpec::Core::Pending::SkipDeclaredInExample?
204
+ result = test_tracing_component.trace(Ext::BEFORE_STEP_SPAN_NAME, type: Ext::STEP_SPAN_TYPE) do
205
+ super
206
+ rescue ::RSpec::Core::Pending::SkipDeclaredInExample => e
207
+ skip_exception = e
208
+ end
209
+
210
+ raise skip_exception if skip_exception
211
+
212
+ result
213
+ end
214
+
215
+ def run_after_example
216
+ return super unless @datadog_test_tracing_component
217
+
218
+ exception_before_hooks = exception
219
+
220
+ test_tracing_component.trace(Ext::AFTER_STEP_SPAN_NAME, type: Ext::STEP_SPAN_TYPE) do |span|
221
+ result = super
222
+ span&.failed!(exception: exception) if exception && exception != exception_before_hooks
223
+ result
224
+ end
225
+ end
226
+
227
+ def datadog_tracing_enabled?
228
+ Datadog.configuration.ci.enabled &&
229
+ datadog_configuration[:enabled] &&
230
+ (!::RSpec.configuration.dry_run? || datadog_configuration[:dry_run_enabled])
231
+ end
232
+
196
233
  def build_test_tags
197
234
  # @type var tags : Hash[String, String]
198
235
  tags = {
@@ -376,7 +413,7 @@ module Datadog
376
413
  end
377
414
 
378
415
  def test_tracing_component
379
- Datadog.send(:components).test_tracing
416
+ @datadog_test_tracing_component || Datadog.send(:components).test_tracing
380
417
  end
381
418
 
382
419
  def test_retries_component
@@ -13,6 +13,10 @@ module Datadog
13
13
  ENV_ENABLED = "DD_TRACE_RSPEC_ENABLED"
14
14
  ENV_DATADOG_FORMATTER_ENABLED = "DD_TRACE_RSPEC_DATADOG_FORMATTER_ENABLED"
15
15
 
16
+ STEP_SPAN_TYPE = "step"
17
+ BEFORE_STEP_SPAN_NAME = "before"
18
+ AFTER_STEP_SPAN_NAME = "after"
19
+
16
20
  # Metadata keys
17
21
  METADATA_DD_RETRIES = :dd_retries
18
22
  METADATA_DD_RETRY_RESULTS = :dd_retry_results
@@ -459,13 +459,11 @@ module Datadog
459
459
  def enrich_coverage_with_static_dependencies(coverage)
460
460
  return unless @static_dependencies_tracking_enabled
461
461
 
462
- static_dependencies_map = {}
463
- coverage.each_key do |file|
464
- static_dependencies_map.merge!(
462
+ coverage.keys.each do |file|
463
+ coverage.merge!(
465
464
  Datadog::CI::SourceCode::StaticDependencies.fetch_static_dependencies(file)
466
465
  )
467
466
  end
468
- coverage.merge!(static_dependencies_map)
469
467
  end
470
468
 
471
469
  def ensure_test_source_covered(test_source_file, coverage)
@@ -493,9 +491,8 @@ module Datadog
493
491
 
494
492
  enrich_coverage_with_static_dependencies(coverage)
495
493
 
496
- # Avoid normalizing and deduplicating paths on the test thread. The
497
- # writer does that when it serializes the event. Telemetry only needs
498
- # an estimate and may count overlaps between the two collections.
494
+ # Avoid normalizing paths on the test thread. The writer serializes
495
+ # each entry and telemetry only needs the resulting entry count.
499
496
  Telemetry.code_coverage_files(coverage.size + custom_impacted_files.size)
500
497
 
501
498
  files = Coverage::Files.new(coverage, custom_impacted_files)
@@ -8,7 +8,7 @@ module Datadog
8
8
  module TestImpactAnalysis
9
9
  module Coverage
10
10
  # Keeps native coverage and custom impacted files together and
11
- # normalizes them as one set.
11
+ # normalizes their paths for serialization.
12
12
  #
13
13
  # @internal
14
14
  class Files
@@ -33,9 +33,9 @@ module Datadog
33
33
  end
34
34
 
35
35
  # Writes the complete MessagePack files array. The native fast path
36
- # combines absolute-path classification, immutable-root slicing,
37
- # stable deduplication, and filename-entry packing. Any shape it does
38
- # not support falls back before writing bytes.
36
+ # combines absolute-path classification, immutable-root slicing, and
37
+ # filename-entry packing. Any shape it does not support falls back
38
+ # before writing bytes.
39
39
  def write_to(packer)
40
40
  if FileSerialization.respond_to?(:pack_files) &&
41
41
  (packed_files = FileSerialization.pack_files(
@@ -85,7 +85,7 @@ module Datadog
85
85
  end
86
86
  files << relative_file unless relative_file.empty?
87
87
  end
88
- files.uniq
88
+ files
89
89
  end
90
90
  end
91
91
  end
@@ -14,6 +14,12 @@ module Datadog
14
14
  class TestSession < ConcurrentSpan
15
15
  attr_accessor :estimated_total_tests_count, :distributed
16
16
 
17
+ def initialize(tracer_span)
18
+ super
19
+
20
+ @distributed = false
21
+ end
22
+
17
23
  # Finishes the current test session.
18
24
  # @return [void]
19
25
  def finish
@@ -82,7 +82,7 @@ module Datadog
82
82
  store_component_state if test_session.distributed
83
83
  end
84
84
 
85
- def start_test_session(service: nil, tags: {}, estimated_total_tests_count: 0, distributed: false, local_test_suites_mode: true)
85
+ def start_test_session(service: nil, tags: {}, estimated_total_tests_count: 0, distributed: nil, local_test_suites_mode: true)
86
86
  return skip_tracing unless test_suite_level_visibility_enabled
87
87
 
88
88
  @local_test_suites_mode = local_test_suites_mode
@@ -91,7 +91,7 @@ module Datadog
91
91
 
92
92
  test_session = maybe_remote_context.start_test_session(service: service, tags: tags)
93
93
  test_session.estimated_total_tests_count = estimated_total_tests_count
94
- test_session.distributed = distributed
94
+ test_session.distributed = distributed unless distributed.nil?
95
95
 
96
96
  on_test_session_started(test_session)
97
97
 
@@ -321,8 +321,6 @@ module Datadog
321
321
  DeprecatedTotalCoverageMetric.extract_lines_pct(test_session)
322
322
 
323
323
  Telemetry.event_finished(test_session)
324
-
325
- Utils::FileStorage.cleanup
326
324
  end
327
325
 
328
326
  def on_test_module_finished(test_module)
@@ -9,7 +9,7 @@ module Datadog
9
9
  end
10
10
 
11
11
  def start_test_session(
12
- service: nil, tags: {}, estimated_total_tests_count: 0, distributed: false, local_test_suites_mode: true
12
+ service: nil, tags: {}, estimated_total_tests_count: 0, distributed: nil, local_test_suites_mode: true
13
13
  )
14
14
  skip_tracing
15
15
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
- require "tempfile"
4
+ require "securerandom"
5
+ require "tmpdir"
5
6
 
6
7
  module Datadog
7
8
  module CI
@@ -9,18 +10,30 @@ module Datadog
9
10
  # FileStorage module provides functionality for storing and retrieving arbitrary Ruby objects in a temp file
10
11
  # to share them between processes.
11
12
  module FileStorage
13
+ class MissingNamespaceError < StandardError; end
14
+
12
15
  TEMP_DIR = File.join(Dir.tmpdir, "datadog-ci-storage")
16
+ ENV_NAMESPACE = "DD_CIVISIBILITY_PARALLEL_TESTS_RUN_ID"
13
17
 
14
18
  def self.store(key, value)
15
19
  ensure_temp_dir_exists
16
20
  file_path = file_path_for(key)
21
+ temporary_path = File.join(storage_dir, "dd-ci-#{SecureRandom.uuid}.tmp")
17
22
 
18
- File.binwrite(file_path, Marshal.dump(value))
23
+ File.open(temporary_path, File::WRONLY | File::CREAT | File::EXCL, 0o600) do |file|
24
+ file.binmode
25
+ file.write(Marshal.dump(value))
26
+ file.flush
27
+ file.fsync
28
+ end
29
+ File.rename(temporary_path, file_path)
19
30
 
20
31
  true
21
32
  rescue => e
22
- Datadog.logger.error("Failed to store data for key '#{key}': #{e.class} - #{e.message}")
33
+ Datadog.logger.error("Failed to store data for key '#{key}': #{e.class}")
23
34
  false
35
+ ensure
36
+ FileUtils.rm_f(temporary_path) if temporary_path
24
37
  end
25
38
 
26
39
  def self.retrieve(key)
@@ -29,27 +42,54 @@ module Datadog
29
42
 
30
43
  Marshal.load(File.binread(file_path))
31
44
  rescue => e
32
- Datadog.logger.error("Failed to retrieve data for key '#{key}': #{e.class} - #{e.message}")
45
+ Datadog.logger.error("Failed to retrieve data for key '#{key}': #{e.class}")
33
46
  nil
34
47
  end
35
48
 
36
- def self.cleanup
37
- return false unless Dir.exist?(TEMP_DIR)
49
+ def self.cleanup(namespace)
50
+ directory = storage_dir(namespace)
51
+ return false unless Dir.exist?(directory)
38
52
 
39
- FileUtils.rm_rf(TEMP_DIR)
53
+ FileUtils.rm_rf(directory)
40
54
  true
41
55
  rescue => e
42
- Datadog.logger.error("Failed to cleanup storage directory: #{e.class} - #{e.message}")
56
+ Datadog.logger.error("Failed to cleanup storage directory: #{e.class}")
43
57
  false
44
58
  end
59
+ private_class_method :cleanup
60
+
61
+ def self.with_new_namespace
62
+ previous_namespace = ENV[ENV_NAMESPACE]
63
+ namespace = SecureRandom.uuid
64
+ ENV[ENV_NAMESPACE] = namespace
65
+
66
+ yield namespace
67
+ ensure
68
+ cleanup(namespace) if namespace
69
+
70
+ if previous_namespace
71
+ ENV[ENV_NAMESPACE] = previous_namespace
72
+ else
73
+ ENV.delete(ENV_NAMESPACE)
74
+ end
75
+ end
45
76
 
46
77
  def self.ensure_temp_dir_exists
47
- FileUtils.mkdir_p(TEMP_DIR) unless Dir.exist?(TEMP_DIR)
78
+ FileUtils.mkdir_p(storage_dir)
48
79
  end
49
80
 
50
81
  def self.file_path_for(key)
51
82
  sanitized_key = key.to_s.gsub(/[^a-zA-Z0-9_-]/, "_")
52
- File.join(TEMP_DIR, "dd-ci-#{sanitized_key}.dat")
83
+ File.join(storage_dir, "dd-ci-#{sanitized_key}.dat")
84
+ end
85
+
86
+ def self.storage_dir(namespace = ENV[ENV_NAMESPACE])
87
+ if namespace.nil? || namespace.empty?
88
+ raise MissingNamespaceError, "File storage namespace is not set"
89
+ end
90
+
91
+ sanitized_namespace = namespace.gsub(/[^a-zA-Z0-9_-]/, "_")
92
+ File.join(TEMP_DIR, sanitized_namespace)
53
93
  end
54
94
  end
55
95
  end
@@ -4,7 +4,7 @@ module Datadog
4
4
  module CI
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 37
7
+ MINOR = 38
8
8
  PATCH = 0
9
9
  PRE = nil
10
10
  BUILD = nil
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.37.0
4
+ version: 1.38.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.