datadog-ci 1.0.0.beta6 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a905eacfa2face6f297e027aa344b9b36f99d7f7f2120ab6946b2e9c650c390b
4
- data.tar.gz: 537bd49690464320a74f88d9a0c392dcac10b83575407d703cf756a71ea8c29e
3
+ metadata.gz: 73bd9b05aba53e1197420cc0e4a529339b6526e13775467fa326b4949e44043a
4
+ data.tar.gz: 7613c476a2a3f2cf28d7dd99dd53da2fa6c921c9a03de18a6997f25e3fe0f27a
5
5
  SHA512:
6
- metadata.gz: 8c87d7be67c31a970d0f6e75fcc4a0305719604afe02eac4061fd9c43a2539632f7a6f9199aafdaf55438913e721280ec2e5ac30f75d5921dd4bb3bdc1638f5a
7
- data.tar.gz: '0838f7ce0df3bdf19a9a9c2c0df6c1b8ef0a96421e7043bfa339b6aa8d7a04f204a8c2a0dcb1cccb80f13df4a47a5da16c127a756d5064512120ea35a9beb8d0'
6
+ metadata.gz: fc66a07d4ccceb71f18b26d290e387b6b403d7efbb46169ced3db9ad0e67e2f29ec163bdaa43a7f7508c6511ca0c68d99257d32d3a25e62b42e00219d7ef44a1
7
+ data.tar.gz: 6fb71c67ecea77fc61238fa7f739192a7670f51d8f4c052d436d2f4d69743f1202f27e27cf84fa76ce74860145d13badb00862c1c03b37d93dc9a33e597afb3d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.0.1] - 2024-06-11
4
+
5
+ ### Fixed
6
+ * multi threaded code coverage support for datadog_cov ([#189][])
7
+ * code coverage extension fixes and improvements ([#171][])
8
+
9
+ ## [1.0.0] - 2024-06-06
10
+
11
+
12
+ ### Changed
13
+ * automatically trace with correct time even when time is stubbed by timecop ([#185][])
14
+ * depend on gem datadog ~> 2.0 ([#190][])
15
+
3
16
  ## [1.0.0.beta6] - 2024-05-29
4
17
 
5
18
  ### Added
@@ -254,7 +267,9 @@ Currently test suite level visibility is not used by our instrumentation: it wil
254
267
 
255
268
  - Ruby versions < 2.7 no longer supported ([#8][])
256
269
 
257
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0.beta6...main
270
+ [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.1...main
271
+ [1.0.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0...v1.0.1
272
+ [1.0.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0.beta6...v1.0.0
258
273
  [1.0.0.beta6]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0.beta5...v1.0.0.beta6
259
274
  [1.0.0.beta5]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0.beta4...v1.0.0.beta5
260
275
  [1.0.0.beta4]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0.beta3...v1.0.0.beta4
@@ -353,9 +368,13 @@ Currently test suite level visibility is not used by our instrumentation: it wil
353
368
  [#167]: https://github.com/DataDog/datadog-ci-rb/issues/167
354
369
  [#168]: https://github.com/DataDog/datadog-ci-rb/issues/168
355
370
  [#170]: https://github.com/DataDog/datadog-ci-rb/issues/170
371
+ [#171]: https://github.com/DataDog/datadog-ci-rb/issues/171
356
372
  [#172]: https://github.com/DataDog/datadog-ci-rb/issues/172
357
373
  [#173]: https://github.com/DataDog/datadog-ci-rb/issues/173
358
374
  [#174]: https://github.com/DataDog/datadog-ci-rb/issues/174
359
375
  [#175]: https://github.com/DataDog/datadog-ci-rb/issues/175
360
376
  [#180]: https://github.com/DataDog/datadog-ci-rb/issues/180
361
- [#183]: https://github.com/DataDog/datadog-ci-rb/issues/183
377
+ [#183]: https://github.com/DataDog/datadog-ci-rb/issues/183
378
+ [#185]: https://github.com/DataDog/datadog-ci-rb/issues/185
379
+ [#189]: https://github.com/DataDog/datadog-ci-rb/issues/189
380
+ [#190]: https://github.com/DataDog/datadog-ci-rb/issues/190
data/README.md CHANGED
@@ -1,13 +1,23 @@
1
- # Datadog CI Visibility for Ruby
1
+ # Datadog Test Visibility for Ruby
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/datadog-ci.svg)](https://badge.fury.io/rb/datadog-ci)
4
4
  [![YARD documentation](https://img.shields.io/badge/YARD-documentation-blue)](https://datadoghq.dev/datadog-ci-rb/)
5
5
  [![codecov](https://codecov.io/gh/DataDog/datadog-ci-rb/branch/main/graph/badge.svg)](https://app.codecov.io/gh/DataDog/datadog-ci-rb/branch/main)
6
6
  [![CircleCI](https://dl.circleci.com/status-badge/img/gh/DataDog/datadog-ci-rb/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/DataDog/datadog-ci-rb/tree/main)
7
7
 
8
- Datadog's Ruby Library for instrumenting your test and continuous integration pipeline.
8
+ Datadog's Ruby Library for instrumenting your tests.
9
9
  Learn more on our [official website](https://docs.datadoghq.com/tests/) and check out our [documentation for this library](https://docs.datadoghq.com/tests/setup/ruby/?tab=cloudciprovideragentless).
10
10
 
11
+ ## Features
12
+
13
+ - [Test Visibility](https://docs.datadoghq.com/tests/) - collect metrics and results for your tests
14
+ - [Intelligent test runner](https://docs.datadoghq.com/intelligent_test_runner/) - save time by selectively running only tests affected by code changes
15
+ - [Search and manage CI tests](https://docs.datadoghq.com/tests/search/)
16
+ - [Enhance developer workflows](https://docs.datadoghq.com/tests/developer_workflows)
17
+ - [Flaky test management](https://docs.datadoghq.com/tests/guides/flaky_test_management/)
18
+ - [Add custom measures to your tests](https://docs.datadoghq.com/tests/guides/add_custom_measures/?tab=ruby)
19
+ - [Browser tests integration with Datadog RUM](https://docs.datadoghq.com/tests/browser_tests)
20
+
11
21
  ## Installation
12
22
 
13
23
  Add to your Gemfile.
@@ -22,210 +32,10 @@ end
22
32
 
23
33
  If you used [test visibility for Ruby](https://docs.datadoghq.com/tests/setup/ruby/) with [ddtrace](https://github.com/datadog/dd-trace-rb) gem, check out our [upgrade guide](/docs/UpgradeGuide.md).
24
34
 
25
- ## Usage
26
-
27
- ### RSpec
28
-
29
- To activate `RSpec` integration, add this to the `spec_helper.rb` file:
30
-
31
- ```ruby
32
- require 'rspec'
33
- require 'datadog/ci'
34
-
35
- # Only activates test instrumentation on CI
36
- if ENV["DD_ENV"] == "ci"
37
- Datadog.configure do |c|
38
- # The name of the service or library under test
39
- c.service = 'my-ruby-app'
40
- c.ci.enabled = true
41
- c.ci.instrument :rspec
42
- end
43
- end
44
- ```
45
-
46
- ### Minitest
47
-
48
- The Minitest integration will trace all executions of tests when using `minitest` test framework.
49
-
50
- To activate your integration, use the `Datadog.configure` method:
51
-
52
- ```ruby
53
- require 'minitest'
54
- require 'datadog/ci'
55
-
56
- # Only activates test instrumentation on CI
57
- if ENV["DD_ENV"] == "ci"
58
- # Configure default Minitest integration
59
- Datadog.configure do |c|
60
- # The name of the service or library under test
61
- c.service = 'my-ruby-app'
62
- c.ci.enabled = true
63
- c.ci.instrument :minitest
64
- end
65
- end
66
- ```
67
-
68
- > [!IMPORTANT]
69
- > When using `minitest/autorun` the order of requires matters: `datadog/ci` must be
70
- > always required before `minitest/autorun`.
71
-
72
- Example using `minitest/autorun`
73
-
74
- ```ruby
75
- require 'datadog/ci'
76
- require 'minitest/autorun'
77
-
78
- if ENV["DD_ENV"] == "ci"
79
- Datadog.configure do |c|
80
- c.service = 'my-ruby-app'
81
- c.ci.enabled = true
82
- c.ci.instrument :minitest
83
- end
84
- end
85
- ```
86
-
87
- ### Cucumber
88
-
89
- Activate `Cucumber` integration with configuration
90
-
91
- ```ruby
92
- require 'cucumber'
93
- require 'datadog/ci'
94
-
95
- # Only activates test instrumentation on CI
96
- if ENV["DD_ENV"] == "ci"
97
- Datadog.configure do |c|
98
- # The name of the service or library under test
99
- c.service = 'my-ruby-app'
100
- c.ci.enabled = true
101
- c.ci.instrument :cucumber
102
- end
103
- end
104
- ```
105
-
106
- ### Instrumentation options
107
-
108
- Configuration `ci.instrument` accepts the following optional parameters:
35
+ ## Setup
109
36
 
110
- - `enabled` (default: `true`) - defines whether tests should be traced (useful for temporarily disabling tracing)
111
- - `service_name` - name of the service or library under test (when you want it to be different for a test framework)
112
-
113
- Example usage:
114
-
115
- ```ruby
116
- Datadog.configure do |c|
117
- c.service = 'my-ruby-app'
118
- c.ci.enabled = true
119
- c.ci.instrument :cucumber, service_name: 'my-cucumber-features', enabled: true
120
- c.ci.instrument :minitest, service_name: 'my-unit-tests', enabled: false
121
- end
122
- ```
123
-
124
- ## Agentless mode
125
-
126
- If you are using a cloud CI provider without access to the underlying worker nodes, such as GitHub Actions or CircleCI, configure the library to use the Agentless mode. For this, set the following environment variables:
127
- `DD_CIVISIBILITY_AGENTLESS_ENABLED=true (Required)` and `DD_API_KEY=your_secret_api_key (Required)`.
128
-
129
- Additionally, configure which [Datadog site](https://docs.datadoghq.com/getting_started/site/) you want to send data to:
130
- `DD_SITE=your.datadoghq.com` (datadoghq.com by default).
131
-
132
- Agentless mode can also be enabled via `Datadog.configure` (but don't forget to set DD_API_KEY environment variable):
133
-
134
- ```ruby
135
- Datadog.configure { |c| c.ci.agentless_mode_enabled = true }
136
- ```
137
-
138
- ## Additional configuration
139
-
140
- ### Add tracing instrumentations
141
-
142
- It can be useful to have rich tracing information about your tests that includes time spent performing database operations
143
- or other external calls like here:
144
-
145
- ![Test trace with redis instrumented](./docs/screenshots/test-trace-with-redis.png)
146
-
147
- To achieve this, add Datadog tracing instrumentations in your `Datadog.configure` block:
148
-
149
- ```ruby
150
- Datadog.configure do |c|
151
- # ... ci configs and instrumentation here ...
152
- c.tracing.instrument :redis
153
- c.tracing.instrument :pg
154
- end
155
- ```
156
-
157
- ...or enable auto instrumentation in your test_helper/spec_helper:
158
-
159
- ```ruby
160
- require "datadog/auto_instrument"
161
- ```
162
-
163
- Note: in CI mode these traces are going to be submitted to CI Visibility,
164
- they will **not** show up in Datadog APM.
165
-
166
- For the full list of available instrumentations see [datadog documentation](https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md)
167
-
168
- ### WebMock
169
-
170
- [WebMock](https://github.com/bblimke/webmock)
171
- is a popular Ruby library that stubs HTTP requests when running tests.
172
- By default it fails when used together with datadog-ci as traces are being sent
173
- to Datadog via HTTP calls.
174
-
175
- In order to allow HTTP connections for Datadog backend you would need to configure
176
- Webmock accordingly.
177
-
178
- ```ruby
179
- # when using agentless mode
180
- WebMock.disable_net_connect!(:allow => /datadoghq/)
181
-
182
- # when using agent
183
- WebMock.disable_net_connect!(:allow_localhost => true)
184
-
185
- # or for more granular setting set your agent URL
186
- WebMock.disable_net_connect!(:allow => "localhost:8126")
187
- ```
188
-
189
- ### VCR
190
-
191
- [VCR](https://github.com/vcr/vcr) is another popular testing library for HTTP interactions.
192
-
193
- It requires additional configuration to correctly work with datadog-ci:
194
-
195
- ```ruby
196
- VCR.configure do |config|
197
- # ... your usual configuration here ...
198
-
199
- # when using agent
200
- config.ignore_hosts "127.0.0.1", "localhost"
201
-
202
- # when using agentless mode
203
- config.ignore_request do |request|
204
- # ignore all requests to datadoghq hosts
205
- request.uri =~ /datadoghq/
206
- end
207
- end
208
- ```
209
-
210
- ### Disabling startup logs
211
-
212
- Startup logs produce a report of tracing state when the application is initially configured.
213
- These logs are activated by default in test mode, if you don't want them you can disable this
214
- via `DD_TRACE_STARTUP_LOGS=0` or in the `Datadog.configure` block:
215
-
216
- ```ruby
217
- Datadog.configure { |c| c.diagnostics.startup_logs.enabled = false }
218
- ```
219
-
220
- ### Enabling debug mode
221
-
222
- Switching the library into debug mode will produce verbose, detailed logs about tracing activity, including any suppressed errors. This output can be helpful in identifying errors, confirming trace output, or catching HTTP transport issues.
223
-
224
- You can enable this via `DD_TRACE_DEBUG=1` or in the `Datadog.configure` block:
225
-
226
- ```ruby
227
- Datadog.configure { |c| c.diagnostics.debug = true }
228
- ```
37
+ - [Test visibility setup](https://docs.datadoghq.com/tests/setup/ruby/?tab=cloudciprovideragentless)
38
+ - [Intelligent test runner setup](https://docs.datadoghq.com/intelligent_test_runner/setup/ruby) (test visibility setup is required before setting up intelligent test runner)
229
39
 
230
40
  ## Contributing
231
41
 
@@ -1,55 +1,54 @@
1
1
  #include <ruby.h>
2
2
  #include <ruby/debug.h>
3
3
 
4
- // constants
5
- #define DD_COV_TARGET_FILES 1
6
- #define DD_COV_TARGET_LINES 2
4
+ #define PROFILE_FRAMES_BUFFER_SIZE 1
7
5
 
8
- static int is_prefix(VALUE prefix, const char *str)
6
+ // threading modes
7
+ #define SINGLE_THREADED_COVERAGE_MODE 0
8
+ #define MULTI_THREADED_COVERAGE_MODE 1
9
+
10
+ char *ruby_strndup(const char *str, size_t size)
9
11
  {
10
- if (prefix == Qnil)
11
- {
12
- return 0;
13
- }
12
+ char *dup;
14
13
 
15
- const char *c_prefix = RSTRING_PTR(prefix);
16
- if (c_prefix == NULL)
17
- {
18
- return 0;
19
- }
14
+ dup = xmalloc(size + 1);
15
+ memcpy(dup, str, size);
16
+ dup[size] = '\0';
20
17
 
21
- long prefix_len = RSTRING_LEN(prefix);
22
- if (strncmp(c_prefix, str, prefix_len) == 0)
23
- {
24
- return 1;
25
- }
26
- else
27
- {
28
- return 0;
29
- }
18
+ return dup;
30
19
  }
31
20
 
32
21
  // Data structure
33
22
  struct dd_cov_data
34
23
  {
35
- VALUE root;
36
- VALUE ignored_path;
37
- int mode;
24
+ char *root;
25
+ long root_len;
26
+
27
+ char *ignored_path;
28
+ long ignored_path_len;
29
+
38
30
  VALUE coverage;
31
+
32
+ uintptr_t last_filename_ptr;
33
+
34
+ // for single threaded mode: thread that is being covered
35
+ VALUE th_covered;
36
+
37
+ int threading_mode;
39
38
  };
40
39
 
41
40
  static void dd_cov_mark(void *ptr)
42
41
  {
43
42
  struct dd_cov_data *dd_cov_data = ptr;
44
43
  rb_gc_mark_movable(dd_cov_data->coverage);
45
- rb_gc_mark_movable(dd_cov_data->root);
46
- rb_gc_mark_movable(dd_cov_data->ignored_path);
44
+ rb_gc_mark_movable(dd_cov_data->th_covered);
47
45
  }
48
46
 
49
47
  static void dd_cov_free(void *ptr)
50
48
  {
51
49
  struct dd_cov_data *dd_cov_data = ptr;
52
-
50
+ xfree(dd_cov_data->root);
51
+ xfree(dd_cov_data->ignored_path);
53
52
  xfree(dd_cov_data);
54
53
  }
55
54
 
@@ -57,8 +56,7 @@ static void dd_cov_compact(void *ptr)
57
56
  {
58
57
  struct dd_cov_data *dd_cov_data = ptr;
59
58
  dd_cov_data->coverage = rb_gc_location(dd_cov_data->coverage);
60
- dd_cov_data->root = rb_gc_location(dd_cov_data->root);
61
- dd_cov_data->ignored_path = rb_gc_location(dd_cov_data->ignored_path);
59
+ dd_cov_data->th_covered = rb_gc_location(dd_cov_data->th_covered);
62
60
  }
63
61
 
64
62
  const rb_data_type_t dd_cov_data_type = {
@@ -74,10 +72,15 @@ static VALUE dd_cov_allocate(VALUE klass)
74
72
  {
75
73
  struct dd_cov_data *dd_cov_data;
76
74
  VALUE obj = TypedData_Make_Struct(klass, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
75
+
77
76
  dd_cov_data->coverage = rb_hash_new();
78
- dd_cov_data->root = Qnil;
79
- dd_cov_data->ignored_path = Qnil;
80
- dd_cov_data->mode = DD_COV_TARGET_FILES;
77
+ dd_cov_data->root = NULL;
78
+ dd_cov_data->root_len = 0;
79
+ dd_cov_data->ignored_path = NULL;
80
+ dd_cov_data->ignored_path_len = 0;
81
+ dd_cov_data->last_filename_ptr = 0;
82
+ dd_cov_data->threading_mode = MULTI_THREADED_COVERAGE_MODE;
83
+
81
84
  return obj;
82
85
  }
83
86
 
@@ -85,7 +88,6 @@ static VALUE dd_cov_allocate(VALUE klass)
85
88
  static VALUE dd_cov_initialize(int argc, VALUE *argv, VALUE self)
86
89
  {
87
90
  VALUE opt;
88
- int mode;
89
91
 
90
92
  rb_scan_args(argc, argv, "10", &opt);
91
93
  VALUE rb_root = rb_hash_lookup(opt, ID2SYM(rb_intern("root")));
@@ -93,113 +95,141 @@ static VALUE dd_cov_initialize(int argc, VALUE *argv, VALUE self)
93
95
  {
94
96
  rb_raise(rb_eArgError, "root is required");
95
97
  }
96
-
97
98
  VALUE rb_ignored_path = rb_hash_lookup(opt, ID2SYM(rb_intern("ignored_path")));
98
99
 
99
- VALUE rb_mode = rb_hash_lookup(opt, ID2SYM(rb_intern("mode")));
100
- if (!RTEST(rb_mode) || rb_mode == ID2SYM(rb_intern("files")))
100
+ VALUE rb_threading_mode = rb_hash_lookup(opt, ID2SYM(rb_intern("threading_mode")));
101
+ int threading_mode;
102
+ if (rb_threading_mode == ID2SYM(rb_intern("multi")))
101
103
  {
102
- mode = DD_COV_TARGET_FILES;
104
+ threading_mode = MULTI_THREADED_COVERAGE_MODE;
103
105
  }
104
- else if (rb_mode == ID2SYM(rb_intern("lines")))
106
+ else if (rb_threading_mode == ID2SYM(rb_intern("single")))
105
107
  {
106
- mode = DD_COV_TARGET_LINES;
108
+ threading_mode = SINGLE_THREADED_COVERAGE_MODE;
107
109
  }
108
110
  else
109
111
  {
110
- rb_raise(rb_eArgError, "mode is invalid");
112
+ rb_raise(rb_eArgError, "threading mode is invalid");
111
113
  }
112
114
 
113
115
  struct dd_cov_data *dd_cov_data;
114
116
  TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
115
117
 
116
- dd_cov_data->root = rb_root;
117
- dd_cov_data->ignored_path = rb_ignored_path;
118
- dd_cov_data->mode = mode;
118
+ dd_cov_data->threading_mode = threading_mode;
119
+ dd_cov_data->root_len = RSTRING_LEN(rb_root);
120
+ dd_cov_data->root = ruby_strndup(RSTRING_PTR(rb_root), dd_cov_data->root_len);
121
+
122
+ if (RTEST(rb_ignored_path))
123
+ {
124
+ dd_cov_data->ignored_path_len = RSTRING_LEN(rb_ignored_path);
125
+ dd_cov_data->ignored_path = ruby_strndup(RSTRING_PTR(rb_ignored_path), dd_cov_data->ignored_path_len);
126
+ }
119
127
 
120
128
  return Qnil;
121
129
  }
122
130
 
123
- static void dd_cov_update_line_coverage(rb_event_flag_t event, VALUE data, VALUE self, ID id, VALUE klass)
131
+ static void dd_cov_update_coverage(rb_event_flag_t event, VALUE data, VALUE self, ID id, VALUE klass)
124
132
  {
125
133
  struct dd_cov_data *dd_cov_data;
126
134
  TypedData_Get_Struct(data, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
127
135
 
128
- const char *filename = rb_sourcefile();
129
- if (filename == NULL)
136
+ const char *c_filename = rb_sourcefile();
137
+
138
+ // skip if we cover the same file again
139
+ uintptr_t current_filename_ptr = (uintptr_t)c_filename;
140
+ if (dd_cov_data->last_filename_ptr == current_filename_ptr)
130
141
  {
131
142
  return;
132
143
  }
144
+ dd_cov_data->last_filename_ptr = current_filename_ptr;
133
145
 
134
- // if given filename is not located under the root, we skip it
135
- if (is_prefix(dd_cov_data->root, filename) == 0)
146
+ VALUE top_frame;
147
+ int captured_frames = rb_profile_frames(
148
+ 0 /* stack starting depth */,
149
+ PROFILE_FRAMES_BUFFER_SIZE,
150
+ &top_frame,
151
+ NULL);
152
+
153
+ if (captured_frames != PROFILE_FRAMES_BUFFER_SIZE)
136
154
  {
137
155
  return;
138
156
  }
139
157
 
140
- // if ignored_path is provided and given filename is located under the ignored_path, we skip it too
141
- // this is useful for ignoring bundled gems location
142
- if (RTEST(dd_cov_data->ignored_path) && is_prefix(dd_cov_data->ignored_path, filename) == 1)
158
+ VALUE filename = rb_profile_frame_path(top_frame);
159
+ if (filename == Qnil)
143
160
  {
144
161
  return;
145
162
  }
146
163
 
147
- VALUE rb_str_source_file = rb_str_new2(filename);
148
-
149
- if (dd_cov_data->mode == DD_COV_TARGET_FILES)
164
+ char *filename_ptr = RSTRING_PTR(filename);
165
+ // if the current filename is not located under the root, we skip it
166
+ if (strncmp(dd_cov_data->root, filename_ptr, dd_cov_data->root_len) != 0)
150
167
  {
151
- rb_hash_aset(dd_cov_data->coverage, rb_str_source_file, Qtrue);
152
168
  return;
153
169
  }
154
170
 
155
- // this isn't optimized yet, this is a POC to show that lines coverage is possible
156
- // ITR beta is going to use files coverage, we'll get back to this part when
157
- // we need to implement lines coverage
158
- if (dd_cov_data->mode == DD_COV_TARGET_LINES)
171
+ // if ignored_path is provided and the current filename is located under the ignored_path, we skip it too
172
+ // this is useful for ignoring bundled gems location
173
+ if (dd_cov_data->ignored_path_len != 0 && strncmp(dd_cov_data->ignored_path, filename_ptr, dd_cov_data->ignored_path_len) == 0)
159
174
  {
160
- int line_number = rb_sourceline();
161
- if (line_number <= 0)
162
- {
163
- return;
164
- }
165
-
166
- VALUE rb_lines = rb_hash_aref(dd_cov_data->coverage, rb_str_source_file);
167
- if (rb_lines == Qnil)
168
- {
169
- rb_lines = rb_hash_new();
170
- rb_hash_aset(dd_cov_data->coverage, rb_str_source_file, rb_lines);
171
- }
172
-
173
- rb_hash_aset(rb_lines, INT2FIX(line_number), Qtrue);
175
+ return;
174
176
  }
177
+
178
+ rb_hash_aset(dd_cov_data->coverage, filename, Qtrue);
175
179
  }
176
180
 
177
181
  static VALUE dd_cov_start(VALUE self)
178
182
  {
179
- // get current thread
180
- VALUE thval = rb_thread_current();
181
183
 
182
- // add event hook
183
- rb_thread_add_event_hook(thval, dd_cov_update_line_coverage, RUBY_EVENT_LINE, self);
184
+ struct dd_cov_data *dd_cov_data;
185
+ TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
186
+
187
+ if (dd_cov_data->root_len == 0)
188
+ {
189
+ rb_raise(rb_eRuntimeError, "root is required");
190
+ }
191
+
192
+ if (dd_cov_data->threading_mode == SINGLE_THREADED_COVERAGE_MODE)
193
+ {
194
+ VALUE thval = rb_thread_current();
195
+ rb_thread_add_event_hook(thval, dd_cov_update_coverage, RUBY_EVENT_LINE, self);
196
+ dd_cov_data->th_covered = thval;
197
+ }
198
+ else
199
+ {
200
+ rb_add_event_hook(dd_cov_update_coverage, RUBY_EVENT_LINE, self);
201
+ }
184
202
 
185
203
  return self;
186
204
  }
187
205
 
188
206
  static VALUE dd_cov_stop(VALUE self)
189
207
  {
190
- // get current thread
191
- VALUE thval = rb_thread_current();
192
- // remove event hook for the current thread
193
- rb_thread_remove_event_hook(thval, dd_cov_update_line_coverage);
194
-
195
208
  struct dd_cov_data *dd_cov_data;
196
209
  TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
197
210
 
198
- VALUE cov = dd_cov_data->coverage;
211
+ if (dd_cov_data->threading_mode == SINGLE_THREADED_COVERAGE_MODE)
212
+ {
213
+ VALUE thval = rb_thread_current();
214
+ if (!rb_equal(thval, dd_cov_data->th_covered))
215
+ {
216
+ rb_raise(rb_eRuntimeError, "Coverage was not started by this thread");
217
+ }
218
+
219
+ rb_thread_remove_event_hook(dd_cov_data->th_covered, dd_cov_update_coverage);
220
+ dd_cov_data->th_covered = Qnil;
221
+ }
222
+ else
223
+ {
224
+ rb_remove_event_hook(dd_cov_update_coverage);
225
+ }
226
+
227
+ VALUE res = dd_cov_data->coverage;
199
228
 
200
229
  dd_cov_data->coverage = rb_hash_new();
230
+ dd_cov_data->last_filename_ptr = 0;
201
231
 
202
- return cov;
232
+ return res;
203
233
  }
204
234
 
205
235
  void Init_datadog_cov(void)
@@ -87,7 +87,7 @@ module Datadog
87
87
  return pattern if pattern == "*"
88
88
 
89
89
  # if pattern ends with a slash then it matches everything deeply nested in this directory
90
- pattern += "**" if pattern.end_with?(::File::SEPARATOR)
90
+ pattern << "**" if pattern.end_with?(::File::SEPARATOR)
91
91
 
92
92
  # if pattern doesn't start with a slash then it matches anywhere in the repository
93
93
  if !pattern.start_with?(::File::SEPARATOR, "**#{::File::SEPARATOR}", "*#{::File::SEPARATOR}")
@@ -8,11 +8,11 @@ module Datadog
8
8
  # Responsible for parsing a CODEOWNERS file
9
9
  class Parser
10
10
  DEFAULT_LOCATION = "CODEOWNERS"
11
- POSSIBLE_CODEOWNERS_LOCATIONS = [
12
- "CODEOWNERS",
13
- ".github/CODEOWNERS",
14
- ".gitlab/CODEOWNERS",
15
- "docs/CODEOWNERS"
11
+ POSSIBLE_CODEOWNERS_LOCATIONS = %w[
12
+ CODEOWNERS
13
+ .github/CODEOWNERS
14
+ .gitlab/CODEOWNERS
15
+ docs/CODEOWNERS
16
16
  ].freeze
17
17
 
18
18
  def initialize(root_file_path)
@@ -70,6 +70,17 @@ module Datadog
70
70
  # Choose user defined TraceFlush or default to CI TraceFlush
71
71
  settings.tracing.test_mode.trace_flush = settings.ci.trace_flush || CI::TestVisibility::Flush::Partial.new
72
72
 
73
+ # When timecop is present, Time.now is mocked and .now_without_mock_time is added on Time to
74
+ # get the current time without the mock.
75
+ if timecop?
76
+ settings.time_now_provider = -> do
77
+ Time.now_without_mock_time
78
+ rescue NoMethodError
79
+ # fallback to normal Time.now if Time.now_without_mock_time is not defined for any reason
80
+ Time.now
81
+ end
82
+ end
83
+
73
84
  # startup logs are useless for CI visibility and create noise
74
85
  settings.diagnostics.startup_logs.enabled = false
75
86
 
@@ -118,7 +129,8 @@ module Datadog
118
129
  config_tags: custom_configuration_tags,
119
130
  coverage_writer: coverage_writer,
120
131
  enabled: settings.ci.enabled && settings.ci.itr_enabled,
121
- bundle_location: settings.ci.itr_code_coverage_excluded_bundle_path
132
+ bundle_location: settings.ci.itr_code_coverage_excluded_bundle_path,
133
+ use_single_threaded_coverage: settings.ci.itr_code_coverage_use_single_threaded_mode
122
134
  )
123
135
 
124
136
  git_tree_uploader = Git::TreeUploader.new(api: test_visibility_api)
@@ -191,6 +203,10 @@ module Datadog
191
203
  "Please make sure to set valid site in DD_SITE environment variable"
192
204
  end
193
205
  end
206
+
207
+ def timecop?
208
+ Gem.loaded_specs.key?("timecop") || defined?(Timecop)
209
+ end
194
210
  end
195
211
  end
196
212
  end
@@ -76,6 +76,12 @@ module Datadog
76
76
  end
77
77
  end
78
78
 
79
+ option :itr_code_coverage_use_single_threaded_mode do |o|
80
+ o.type :bool
81
+ o.env CI::Ext::Settings::ENV_ITR_CODE_COVERAGE_USE_SINGLE_THREADED_MODE
82
+ o.default false
83
+ end
84
+
79
85
  define_method(:instrument) do |integration_name, options = {}, &block|
80
86
  return unless enabled
81
87
 
@@ -187,7 +187,7 @@ module Datadog
187
187
  def ok?(result, strict)
188
188
  # in minor update in Cucumber 9.2.0, the arity of the `ok?` method changed
189
189
  parameters = result.method(:ok?).parameters
190
- if parameters == [[:opt, :be_strict]]
190
+ if parameters == [%i[opt be_strict]]
191
191
  result.ok?(strict)
192
192
  else
193
193
  result.ok?(strict: strict)
@@ -23,7 +23,7 @@ module Datadog
23
23
  test_name = full_description.strip
24
24
  if metadata[:description].empty?
25
25
  # for unnamed it blocks this appends something like "example at ./spec/some_spec.rb:10"
26
- test_name += " #{description}"
26
+ test_name << " #{description}"
27
27
  end
28
28
 
29
29
  test_suite_description = fetch_top_level_example_group[:description]
@@ -33,7 +33,7 @@ module Datadog
33
33
  test_name = test_name.sub(test_suite_description, "").strip
34
34
 
35
35
  if ci_queue?
36
- suite_name += " (ci-queue running example [#{test_name}])"
36
+ suite_name = "#{suite_name} (ci-queue running example [#{test_name}])"
37
37
  test_suite_span = CI.start_test_suite(suite_name)
38
38
  end
39
39
 
@@ -83,9 +83,12 @@ module Datadog
83
83
  private
84
84
 
85
85
  def fetch_top_level_example_group
86
- return metadata[:example_group] unless metadata[:example_group][:parent_example_group]
86
+ example_group = metadata[:example_group]
87
+ parent_example_group = example_group[:parent_example_group]
87
88
 
88
- res = metadata[:example_group][:parent_example_group]
89
+ return example_group unless parent_example_group
90
+
91
+ res = parent_example_group
89
92
  while (parent = res[:parent_example_group])
90
93
  res = parent
91
94
  end
@@ -75,12 +75,12 @@ module Datadog
75
75
  end
76
76
 
77
77
  def git_commit_author_name
78
- name, _ = extract_name_email
78
+ name, _email = extract_name_email
79
79
  name
80
80
  end
81
81
 
82
82
  def git_commit_author_email
83
- _, email = extract_name_email
83
+ _name, email = extract_name_email
84
84
  email
85
85
  end
86
86
 
@@ -23,10 +23,7 @@ module Datadog
23
23
  TAG_NODE_NAME = "ci.node.name"
24
24
  TAG_CI_ENV_VARS = "_dd.ci.env_vars"
25
25
 
26
- POSSIBLE_BUNDLE_LOCATIONS = [
27
- "vendor/bundle",
28
- ".bundle"
29
- ].freeze
26
+ POSSIBLE_BUNDLE_LOCATIONS = %w[vendor/bundle .bundle].freeze
30
27
 
31
28
  module_function
32
29
 
@@ -13,15 +13,16 @@ module Datadog
13
13
  ENV_ITR_ENABLED = "DD_CIVISIBILITY_ITR_ENABLED"
14
14
  ENV_GIT_METADATA_UPLOAD_ENABLED = "DD_CIVISIBILITY_GIT_METADATA_UPLOAD_ENABLED"
15
15
  ENV_ITR_CODE_COVERAGE_EXCLUDED_BUNDLE_PATH = "DD_CIVISIBILITY_ITR_CODE_COVERAGE_EXCLUDED_BUNDLE_PATH"
16
+ ENV_ITR_CODE_COVERAGE_USE_SINGLE_THREADED_MODE = "DD_CIVISIBILITY_ITR_CODE_COVERAGE_USE_SINGLE_THREADED_MODE"
16
17
 
17
18
  # Source: https://docs.datadoghq.com/getting_started/site/
18
- DD_SITE_ALLOWLIST = [
19
- "datadoghq.com",
20
- "us3.datadoghq.com",
21
- "us5.datadoghq.com",
22
- "datadoghq.eu",
23
- "ddog-gov.com",
24
- "ap1.datadoghq.com"
19
+ DD_SITE_ALLOWLIST = %w[
20
+ datadoghq.com
21
+ us3.datadoghq.com
22
+ us5.datadoghq.com
23
+ datadoghq.eu
24
+ ddog-gov.com
25
+ ap1.datadoghq.com
25
26
  ].freeze
26
27
  end
27
28
  end
@@ -21,7 +21,7 @@ module Datadog
21
21
  def valid?
22
22
  valid = true
23
23
 
24
- [:test_id, :test_suite_id, :test_session_id, :coverage].each do |key|
24
+ %i[test_id test_suite_id test_session_id coverage].each do |key|
25
25
  next unless send(key).nil?
26
26
 
27
27
  Datadog.logger.warn("citestcov event is invalid: [#{key}] is nil. Event: #{self}")
@@ -22,6 +22,8 @@ module Datadog
22
22
  DEFAULT_BUFFER_MAX_SIZE = 10_000
23
23
  DEFAULT_SHUTDOWN_TIMEOUT = 60
24
24
 
25
+ DEFAULT_INTERVAL = 3
26
+
25
27
  def initialize(transport:, options: {})
26
28
  @transport = transport
27
29
 
@@ -32,7 +34,7 @@ module Datadog
32
34
  self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_RESTART
33
35
 
34
36
  # Workers::IntervalLoop settings
35
- self.loop_base_interval = options[:interval] if options.key?(:interval)
37
+ self.loop_base_interval = options[:interval] || DEFAULT_INTERVAL
36
38
  self.loop_back_off_ratio = options[:back_off_ratio] if options.key?(:back_off_ratio)
37
39
  self.loop_back_off_max = options[:back_off_max] if options.key?(:back_off_max)
38
40
 
@@ -31,7 +31,8 @@ module Datadog
31
31
  api: nil,
32
32
  coverage_writer: nil,
33
33
  enabled: false,
34
- bundle_location: nil
34
+ bundle_location: nil,
35
+ use_single_threaded_coverage: false
35
36
  )
36
37
  @enabled = enabled
37
38
  @api = api
@@ -43,6 +44,7 @@ module Datadog
43
44
  else
44
45
  bundle_location
45
46
  end
47
+ @use_single_threaded_coverage = use_single_threaded_coverage
46
48
 
47
49
  @test_skipping_enabled = false
48
50
  @code_coverage_enabled = false
@@ -186,12 +188,15 @@ module Datadog
186
188
  def coverage_collector
187
189
  Thread.current[:dd_coverage_collector] ||= Coverage::DDCov.new(
188
190
  root: Git::LocalRepository.root,
189
- ignored_path: @bundle_location
191
+ ignored_path: @bundle_location,
192
+ threading_mode: code_coverage_mode
190
193
  )
191
194
  end
192
195
 
193
196
  def load_datadog_cov!
194
197
  require "datadog_cov.#{RUBY_VERSION}_#{RUBY_PLATFORM}"
198
+
199
+ Datadog.logger.debug("Loaded Datadog code coverage collector, using coverage mode: #{code_coverage_mode}")
195
200
  rescue LoadError => e
196
201
  Datadog.logger.error("Failed to load coverage collector: #{e}. Code coverage will not be collected.")
197
202
 
@@ -222,6 +227,10 @@ module Datadog
222
227
  Datadog.logger.debug { "Found #{@skippable_tests.count} skippable tests." }
223
228
  Datadog.logger.debug { "ITR correlation ID: #{@correlation_id}" }
224
229
  end
230
+
231
+ def code_coverage_mode
232
+ @use_single_threaded_coverage ? :single : :multi
233
+ end
225
234
  end
226
235
  end
227
236
  end
@@ -20,13 +20,7 @@ module Datadog
20
20
  "type" => "span_type"
21
21
  ].freeze
22
22
 
23
- REQUIRED_FIELDS = [
24
- "error",
25
- "name",
26
- "resource",
27
- "start",
28
- "duration"
29
- ].freeze
23
+ REQUIRED_FIELDS = %w[error name resource start duration].freeze
30
24
 
31
25
  attr_reader :trace, :span, :meta, :options
32
26
 
@@ -7,11 +7,11 @@ module Datadog
7
7
  module TestVisibility
8
8
  module Serializers
9
9
  class Span < Base
10
- CONTENT_FIELDS = (["trace_id", "span_id", "parent_id"] + Base::CONTENT_FIELDS).freeze
10
+ CONTENT_FIELDS = (%w[trace_id span_id parent_id] + Base::CONTENT_FIELDS).freeze
11
11
 
12
12
  CONTENT_MAP_SIZE = calculate_content_map_size(CONTENT_FIELDS)
13
13
 
14
- REQUIRED_FIELDS = (["trace_id", "span_id"] + Base::REQUIRED_FIELDS).freeze
14
+ REQUIRED_FIELDS = (%w[trace_id span_id] + Base::REQUIRED_FIELDS).freeze
15
15
 
16
16
  def content_fields
17
17
  CONTENT_FIELDS
@@ -8,11 +8,11 @@ module Datadog
8
8
  module TestVisibility
9
9
  module Serializers
10
10
  class TestModule < Base
11
- CONTENT_FIELDS = (["test_session_id", "test_module_id"] + Base::CONTENT_FIELDS).freeze
11
+ CONTENT_FIELDS = (%w[test_session_id test_module_id] + Base::CONTENT_FIELDS).freeze
12
12
 
13
13
  CONTENT_MAP_SIZE = calculate_content_map_size(CONTENT_FIELDS)
14
14
 
15
- REQUIRED_FIELDS = (["test_session_id", "test_module_id"] + Base::REQUIRED_FIELDS).freeze
15
+ REQUIRED_FIELDS = (%w[test_session_id test_module_id] + Base::REQUIRED_FIELDS).freeze
16
16
 
17
17
  def content_fields
18
18
  CONTENT_FIELDS
@@ -8,11 +8,11 @@ module Datadog
8
8
  module TestVisibility
9
9
  module Serializers
10
10
  class TestSession < Base
11
- CONTENT_FIELDS = (["test_session_id"] + Base::CONTENT_FIELDS).freeze
11
+ CONTENT_FIELDS = (%w[test_session_id] + Base::CONTENT_FIELDS).freeze
12
12
 
13
13
  CONTENT_MAP_SIZE = calculate_content_map_size(CONTENT_FIELDS)
14
14
 
15
- REQUIRED_FIELDS = (["test_session_id"] + Base::REQUIRED_FIELDS).freeze
15
+ REQUIRED_FIELDS = (%w[test_session_id] + Base::REQUIRED_FIELDS).freeze
16
16
 
17
17
  def content_fields
18
18
  CONTENT_FIELDS
@@ -8,11 +8,11 @@ module Datadog
8
8
  module TestVisibility
9
9
  module Serializers
10
10
  class TestSuite < Base
11
- CONTENT_FIELDS = (["test_session_id", "test_module_id", "test_suite_id"] + Base::CONTENT_FIELDS).freeze
11
+ CONTENT_FIELDS = (%w[test_session_id test_module_id test_suite_id] + Base::CONTENT_FIELDS).freeze
12
12
 
13
13
  CONTENT_MAP_SIZE = calculate_content_map_size(CONTENT_FIELDS)
14
14
 
15
- REQUIRED_FIELDS = (["test_session_id", "test_module_id", "test_suite_id"] + Base::REQUIRED_FIELDS).freeze
15
+ REQUIRED_FIELDS = (%w[test_session_id test_module_id test_suite_id] + Base::REQUIRED_FIELDS).freeze
16
16
 
17
17
  def content_fields
18
18
  CONTENT_FIELDS
@@ -8,11 +8,11 @@ module Datadog
8
8
  module TestVisibility
9
9
  module Serializers
10
10
  class TestV1 < Base
11
- CONTENT_FIELDS = (["trace_id", "span_id"] + Base::CONTENT_FIELDS).freeze
11
+ CONTENT_FIELDS = (%w[trace_id span_id] + Base::CONTENT_FIELDS).freeze
12
12
 
13
13
  CONTENT_MAP_SIZE = calculate_content_map_size(CONTENT_FIELDS)
14
14
 
15
- REQUIRED_FIELDS = (["trace_id", "span_id"] + Base::REQUIRED_FIELDS).freeze
15
+ REQUIRED_FIELDS = (%w[trace_id span_id] + Base::REQUIRED_FIELDS).freeze
16
16
 
17
17
  def content_fields
18
18
  CONTENT_FIELDS
@@ -8,15 +8,15 @@ module Datadog
8
8
  module TestVisibility
9
9
  module Serializers
10
10
  class TestV2 < TestV1
11
- CONTENT_FIELDS = (["test_session_id", "test_module_id", "test_suite_id"] + TestV1::CONTENT_FIELDS).freeze
11
+ CONTENT_FIELDS = (%w[test_session_id test_module_id test_suite_id] + TestV1::CONTENT_FIELDS).freeze
12
12
 
13
- CONTENT_FIELDS_WITH_ITR_CORRELATION_ID = (CONTENT_FIELDS + ["itr_correlation_id"]).freeze
13
+ CONTENT_FIELDS_WITH_ITR_CORRELATION_ID = (CONTENT_FIELDS + %w[itr_correlation_id]).freeze
14
14
 
15
15
  CONTENT_MAP_SIZE = calculate_content_map_size(CONTENT_FIELDS)
16
16
 
17
17
  CONTENT_MAP_SIZE_WITH_ITR_CORRELATION_ID = calculate_content_map_size(CONTENT_FIELDS_WITH_ITR_CORRELATION_ID)
18
18
 
19
- REQUIRED_FIELDS = (["test_session_id", "test_module_id", "test_suite_id"] + TestV1::REQUIRED_FIELDS).freeze
19
+ REQUIRED_FIELDS = (%w[test_session_id test_module_id test_suite_id] + TestV1::REQUIRED_FIELDS).freeze
20
20
 
21
21
  def content_fields
22
22
  return CONTENT_FIELDS if itr_correlation_id.nil?
@@ -124,6 +124,7 @@ module Datadog
124
124
  def gzipped?(payload)
125
125
  return false if payload.nil? || payload.empty?
126
126
 
127
+ # no-dd-sa
127
128
  first_bytes = payload[0, 2]
128
129
  return false if first_bytes.nil? || first_bytes.empty?
129
130
 
@@ -3,10 +3,10 @@
3
3
  module Datadog
4
4
  module CI
5
5
  module VERSION
6
- MAJOR = "1"
7
- MINOR = "0"
8
- PATCH = "0"
9
- PRE = "beta6"
6
+ MAJOR = 1
7
+ MINOR = 0
8
+ PATCH = 1
9
+ PRE = nil
10
10
  BUILD = nil
11
11
  # PRE and BUILD above are modified for dev gems during gem build GHA workflow
12
12
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog-ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta6
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-29 00:00:00.000000000 Z
11
+ date: 2024-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: datadog
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.0.0.beta2
19
+ version: '2.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 2.0.0.beta2
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: msgpack
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -201,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
201
201
  - !ruby/object:Gem::Version
202
202
  version: 2.0.0
203
203
  requirements: []
204
- rubygems_version: 3.4.19
204
+ rubygems_version: 3.5.9
205
205
  signing_key:
206
206
  specification_version: 4
207
207
  summary: Datadog CI visibility for your ruby application