datadog-ci 1.0.0.beta1 → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/README.md +37 -46
  4. data/lib/datadog/ci/configuration/components.rb +43 -9
  5. data/lib/datadog/ci/configuration/settings.rb +6 -0
  6. data/lib/datadog/ci/contrib/cucumber/formatter.rb +9 -7
  7. data/lib/datadog/ci/contrib/cucumber/patcher.rb +3 -0
  8. data/lib/datadog/ci/contrib/cucumber/step.rb +27 -0
  9. data/lib/datadog/ci/contrib/minitest/hooks.rb +4 -2
  10. data/lib/datadog/ci/contrib/rspec/example.rb +9 -5
  11. data/lib/datadog/ci/ext/environment/providers/local_git.rb +8 -79
  12. data/lib/datadog/ci/ext/environment.rb +11 -16
  13. data/lib/datadog/ci/ext/settings.rb +1 -0
  14. data/lib/datadog/ci/ext/test.rb +5 -0
  15. data/lib/datadog/ci/ext/transport.rb +8 -0
  16. data/lib/datadog/ci/git/local_repository.rb +238 -0
  17. data/lib/datadog/ci/git/packfiles.rb +70 -0
  18. data/lib/datadog/ci/git/search_commits.rb +77 -0
  19. data/lib/datadog/ci/git/tree_uploader.rb +90 -0
  20. data/lib/datadog/ci/git/upload_packfile.rb +66 -0
  21. data/lib/datadog/ci/git/user.rb +29 -0
  22. data/lib/datadog/ci/itr/coverage/event.rb +18 -1
  23. data/lib/datadog/ci/itr/coverage/writer.rb +108 -0
  24. data/lib/datadog/ci/itr/runner.rb +120 -11
  25. data/lib/datadog/ci/itr/skippable.rb +106 -0
  26. data/lib/datadog/ci/span.rb +9 -0
  27. data/lib/datadog/ci/test.rb +19 -12
  28. data/lib/datadog/ci/test_module.rb +2 -2
  29. data/lib/datadog/ci/test_session.rb +2 -2
  30. data/lib/datadog/ci/test_suite.rb +2 -2
  31. data/lib/datadog/ci/test_visibility/null_recorder.rb +4 -1
  32. data/lib/datadog/ci/test_visibility/recorder.rb +47 -9
  33. data/lib/datadog/ci/test_visibility/transport.rb +1 -1
  34. data/lib/datadog/ci/transport/http.rb +24 -4
  35. data/lib/datadog/ci/transport/remote_settings_api.rb +12 -6
  36. data/lib/datadog/ci/utils/configuration.rb +2 -2
  37. data/lib/datadog/ci/utils/git.rb +6 -67
  38. data/lib/datadog/ci/utils/parsing.rb +16 -0
  39. data/lib/datadog/ci/utils/test_run.rb +13 -0
  40. data/lib/datadog/ci/version.rb +1 -1
  41. data/lib/datadog/ci/worker.rb +35 -0
  42. data/lib/datadog/ci.rb +4 -0
  43. metadata +15 -4
@@ -13,13 +13,14 @@ require_relative "../codeowners/parser"
13
13
  require_relative "../ext/app_types"
14
14
  require_relative "../ext/test"
15
15
  require_relative "../ext/environment"
16
- require_relative "../utils/git"
16
+ require_relative "../git/local_repository"
17
17
 
18
18
  require_relative "../span"
19
19
  require_relative "../test"
20
20
  require_relative "../test_session"
21
21
  require_relative "../test_module"
22
22
  require_relative "../test_suite"
23
+ require_relative "../worker"
23
24
 
24
25
  module Datadog
25
26
  module CI
@@ -30,8 +31,11 @@ module Datadog
30
31
  attr_reader :environment_tags, :test_suite_level_visibility_enabled
31
32
 
32
33
  def initialize(
33
- itr:, remote_settings_api:, test_suite_level_visibility_enabled: false,
34
- codeowners: Codeowners::Parser.new(Utils::Git.root).parse
34
+ itr:,
35
+ remote_settings_api:,
36
+ git_tree_upload_worker: DummyWorker.new,
37
+ test_suite_level_visibility_enabled: false,
38
+ codeowners: Codeowners::Parser.new(Git::LocalRepository.root).parse
35
39
  )
36
40
  @test_suite_level_visibility_enabled = test_suite_level_visibility_enabled
37
41
 
@@ -43,6 +47,11 @@ module Datadog
43
47
 
44
48
  @itr = itr
45
49
  @remote_settings_api = remote_settings_api
50
+ @git_tree_upload_worker = git_tree_upload_worker
51
+ end
52
+
53
+ def shutdown!
54
+ @git_tree_upload_worker.stop
46
55
  end
47
56
 
48
57
  def start_test_session(service: nil, tags: {})
@@ -56,6 +65,7 @@ module Datadog
56
65
 
57
66
  test_session = build_test_session(tracer_span, tags)
58
67
 
68
+ @git_tree_upload_worker.perform(test_session.git_repository_url)
59
69
  configure_library(test_session)
60
70
 
61
71
  test_session
@@ -117,21 +127,18 @@ module Datadog
117
127
  test = build_test(tracer_span, tags)
118
128
 
119
129
  @local_context.activate_test(test) do
120
- @itr.start_coverage
121
-
130
+ on_test_started(test)
122
131
  res = block.call(test)
123
132
  on_test_finished(test)
124
-
125
133
  res
126
134
  end
127
135
  end
128
136
  else
129
137
  tracer_span = start_datadog_tracer_span(test_name, span_options)
130
-
131
138
  test = build_test(tracer_span, tags)
132
139
 
133
140
  @local_context.activate_test(test)
134
- @itr.start_coverage
141
+ on_test_started(test)
135
142
 
136
143
  test
137
144
  end
@@ -184,6 +191,9 @@ module Datadog
184
191
  end
185
192
 
186
193
  def deactivate_test_session
194
+ test_session = active_test_session
195
+ on_test_session_finished(test_session) if test_session
196
+
187
197
  @global_context.deactivate_test_session!
188
198
  end
189
199
 
@@ -206,7 +216,25 @@ module Datadog
206
216
  return unless itr_enabled?
207
217
 
208
218
  remote_configuration = @remote_settings_api.fetch_library_settings(test_session)
209
- @itr.configure(remote_configuration.payload, test_session)
219
+ # sometimes we can skip code coverage for default branch if there are no changes in the repository
220
+ # backend needs git metadata uploaded for this test session to check if we can skip code coverage
221
+ if remote_configuration.require_git?
222
+ Datadog.logger.debug { "Library configuration endpoint requires git upload to be finished, waiting..." }
223
+ @git_tree_upload_worker.wait_until_done
224
+
225
+ Datadog.logger.debug { "Requesting library configuration again..." }
226
+ remote_configuration = @remote_settings_api.fetch_library_settings(test_session)
227
+
228
+ if remote_configuration.require_git?
229
+ Datadog.logger.debug { "git metadata upload did not complete in time when configuring library" }
230
+ end
231
+ end
232
+
233
+ @itr.configure(
234
+ remote_configuration.payload,
235
+ test_session: test_session,
236
+ git_tree_upload_worker: @git_tree_upload_worker
237
+ )
210
238
  end
211
239
 
212
240
  def skip_tracing(block = nil)
@@ -373,6 +401,16 @@ module Datadog
373
401
  # TODO: use kind of event system to notify about test finished?
374
402
  def on_test_finished(test)
375
403
  @itr.stop_coverage(test)
404
+ @itr.count_skipped_test(test)
405
+ end
406
+
407
+ def on_test_started(test)
408
+ @itr.mark_if_skippable(test)
409
+ @itr.start_coverage(test)
410
+ end
411
+
412
+ def on_test_session_finished(test_session)
413
+ @itr.write_test_session_tags(test_session)
376
414
  end
377
415
  end
378
416
  end
@@ -14,7 +14,7 @@ module Datadog
14
14
 
15
15
  def initialize(
16
16
  api:,
17
- dd_env: nil,
17
+ dd_env:,
18
18
  serializers_factory: Datadog::CI::TestVisibility::Serializers::Factories::TestLevel,
19
19
  max_payload_size: DEFAULT_MAX_PAYLOAD_SIZE
20
20
  )
@@ -4,6 +4,7 @@ require "delegate"
4
4
  require "datadog/core/transport/http/adapters/net"
5
5
  require "datadog/core/transport/http/env"
6
6
  require "datadog/core/transport/request"
7
+ require "socket"
7
8
 
8
9
  require_relative "gzip"
9
10
  require_relative "../ext/transport"
@@ -20,6 +21,8 @@ module Datadog
20
21
  :compress
21
22
 
22
23
  DEFAULT_TIMEOUT = 30
24
+ MAX_RETRIES = 3
25
+ INITIAL_BACKOFF = 1
23
26
 
24
27
  def initialize(host:, timeout: DEFAULT_TIMEOUT, port: nil, ssl: true, compress: false)
25
28
  @host = host
@@ -29,7 +32,7 @@ module Datadog
29
32
  @compress = compress.nil? ? false : compress
30
33
  end
31
34
 
32
- def request(path:, payload:, headers:, verb: "post")
35
+ def request(path:, payload:, headers:, verb: "post", retries: MAX_RETRIES, backoff: INITIAL_BACKOFF)
33
36
  if compress
34
37
  headers[Ext::Transport::HEADER_CONTENT_ENCODING] = Ext::Transport::CONTENT_ENCODING_GZIP
35
38
  payload = Gzip.compress(payload)
@@ -41,9 +44,7 @@ module Datadog
41
44
  end
42
45
 
43
46
  response = ResponseDecorator.new(
44
- adapter.call(
45
- build_env(path: path, payload: payload, headers: headers, verb: verb)
46
- )
47
+ perform_http_call(path: path, payload: payload, headers: headers, verb: verb, retries: retries, backoff: backoff)
47
48
  )
48
49
 
49
50
  Datadog.logger.debug do
@@ -55,6 +56,25 @@ module Datadog
55
56
 
56
57
  private
57
58
 
59
+ def perform_http_call(path:, payload:, headers:, verb:, retries: MAX_RETRIES, backoff: INITIAL_BACKOFF)
60
+ adapter.call(
61
+ build_env(path: path, payload: payload, headers: headers, verb: verb)
62
+ )
63
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, SocketError, Net::HTTPBadResponse => e
64
+ Datadog.logger.debug("Failed to send request with #{e} (#{e.message})")
65
+
66
+ if retries.positive?
67
+ sleep(backoff)
68
+
69
+ perform_http_call(
70
+ path: path, payload: payload, headers: headers, verb: verb, retries: retries - 1, backoff: backoff * 2
71
+ )
72
+ else
73
+ Datadog.logger.error("Failed to send request after #{MAX_RETRIES} retries")
74
+ raise e
75
+ end
76
+ end
77
+
58
78
  def build_env(path:, payload:, headers:, verb:)
59
79
  env = Datadog::Core::Transport::HTTP::Env.new(
60
80
  Datadog::Core::Transport::Request.new
@@ -5,6 +5,7 @@ require "json"
5
5
  require "datadog/core/environment/identity"
6
6
 
7
7
  require_relative "../ext/transport"
8
+ require_relative "../utils/parsing"
8
9
 
9
10
  module Datadog
10
11
  module CI
@@ -28,7 +29,7 @@ module Datadog
28
29
  return cached unless cached.nil?
29
30
 
30
31
  resp = @http_response
31
- return @json = default_payload if resp.nil? || !resp.ok?
32
+ return @json = default_payload if resp.nil? || !ok?
32
33
 
33
34
  begin
34
35
  @json = JSON.parse(resp.payload).dig(*Ext::Transport::DD_API_SETTINGS_RESPONSE_DIG_KEYS) ||
@@ -39,6 +40,10 @@ module Datadog
39
40
  end
40
41
  end
41
42
 
43
+ def require_git?
44
+ Utils::Parsing.convert_to_bool(payload[Ext::Transport::DD_API_SETTINGS_RESPONSE_REQUIRE_GIT_KEY])
45
+ end
46
+
42
47
  private
43
48
 
44
49
  def default_payload
@@ -46,7 +51,7 @@ module Datadog
46
51
  end
47
52
  end
48
53
 
49
- def initialize(api: nil, dd_env: nil)
54
+ def initialize(dd_env:, api: nil)
50
55
  @api = api
51
56
  @dd_env = dd_env
52
57
  end
@@ -81,10 +86,11 @@ module Datadog
81
86
  "sha" => test_session.git_commit_sha,
82
87
  "test_level" => Ext::Test::ITR_TEST_SKIPPING_MODE,
83
88
  "configurations" => {
84
- "os.platform" => test_session.os_platform,
85
- "os.arch" => test_session.os_architecture,
86
- "runtime.name" => test_session.runtime_name,
87
- "runtime.version" => test_session.runtime_version
89
+ Ext::Test::TAG_OS_PLATFORM => test_session.os_platform,
90
+ Ext::Test::TAG_OS_ARCHITECTURE => test_session.os_architecture,
91
+ Ext::Test::TAG_OS_VERSION => test_session.os_version,
92
+ Ext::Test::TAG_RUNTIME_NAME => test_session.runtime_name,
93
+ Ext::Test::TAG_RUNTIME_VERSION => test_session.runtime_version
88
94
  }
89
95
  }
90
96
  }
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "git"
3
+ require_relative "../git/local_repository"
4
4
 
5
5
  module Datadog
6
6
  module CI
7
7
  module Utils
8
8
  module Configuration
9
9
  def self.fetch_service_name(default)
10
- Datadog.configuration.service_without_fallback || Git.repository_name || default
10
+ Datadog.configuration.service_without_fallback || CI::Git::LocalRepository.repository_name || default
11
11
  end
12
12
  end
13
13
  end
@@ -7,6 +7,12 @@ module Datadog
7
7
  module CI
8
8
  module Utils
9
9
  module Git
10
+ def self.valid_commit_sha?(sha)
11
+ return false if sha.nil?
12
+
13
+ sha.match?(/\A[0-9a-f]{40}\Z/) || sha.match?(/\A[0-9a-f]{64}\Z/)
14
+ end
15
+
10
16
  def self.normalize_ref(ref)
11
17
  return nil if ref.nil?
12
18
 
@@ -19,73 +25,6 @@ module Datadog
19
25
  def self.is_git_tag?(ref)
20
26
  !ref.nil? && ref.include?("tags/")
21
27
  end
22
-
23
- def self.root
24
- return @root if defined?(@root)
25
-
26
- @root = exec_git_command("git rev-parse --show-toplevel") || Dir.pwd
27
- rescue => e
28
- Datadog.logger.debug(
29
- "Unable to read git root: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}"
30
- )
31
- @root = Dir.pwd
32
- end
33
-
34
- def self.relative_to_root(path)
35
- return nil if path.nil?
36
-
37
- git_root = root
38
- return path if git_root.nil?
39
-
40
- path = Pathname.new(File.expand_path(path))
41
- git_root = Pathname.new(git_root)
42
-
43
- path.relative_path_from(git_root).to_s
44
- end
45
-
46
- def self.repository_name
47
- return @repository_name if defined?(@repository_name)
48
-
49
- git_remote_url = exec_git_command("git ls-remote --get-url origin")
50
-
51
- # return git repository name from remote url without .git extension
52
- last_path_segment = git_remote_url.split("/").last if git_remote_url
53
- @repository_name = last_path_segment.gsub(".git", "") if last_path_segment
54
- @repository_name ||= current_folder_name
55
- rescue => e
56
- Datadog.logger.debug(
57
- "Unable to get git remote: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}"
58
- )
59
- @repository_name = current_folder_name
60
- end
61
-
62
- def self.current_folder_name
63
- root_folder = root
64
- if root_folder.nil?
65
- File.basename(Dir.pwd)
66
- else
67
- File.basename(root_folder)
68
- end
69
- end
70
-
71
- def self.exec_git_command(cmd)
72
- out, status = Open3.capture2e(cmd)
73
-
74
- raise "Failed to run git command #{cmd}: #{out}" unless status.success?
75
-
76
- # Sometimes Encoding.default_external is somehow set to US-ASCII which breaks
77
- # commit messages with UTF-8 characters like emojis
78
- # We force output's encoding to be UTF-8 in this case
79
- # This is safe to do as UTF-8 is compatible with US-ASCII
80
- if Encoding.default_external == Encoding::US_ASCII
81
- out = out.force_encoding(Encoding::UTF_8)
82
- end
83
- out.strip! # There's always a "\n" at the end of the command output
84
-
85
- return nil if out.empty?
86
-
87
- out
88
- end
89
28
  end
90
29
  end
91
30
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "pathname"
5
+
6
+ module Datadog
7
+ module CI
8
+ module Utils
9
+ module Parsing
10
+ def self.convert_to_bool(value)
11
+ value.to_s.downcase == "true"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -9,6 +9,19 @@ module Datadog
9
9
 
10
10
  @command = "#{$0} #{ARGV.join(" ")}"
11
11
  end
12
+
13
+ def self.skippable_test_id(test_name, suite, parameters = nil)
14
+ "#{suite}.#{test_name}.#{parameters}"
15
+ end
16
+
17
+ def self.test_parameters(arguments: {}, metadata: {})
18
+ JSON.generate(
19
+ {
20
+ arguments: arguments,
21
+ metadata: metadata
22
+ }
23
+ )
24
+ end
12
25
  end
13
26
  end
14
27
  end
@@ -6,7 +6,7 @@ module Datadog
6
6
  MAJOR = "1"
7
7
  MINOR = "0"
8
8
  PATCH = "0"
9
- PRE = "beta1"
9
+ PRE = "beta2"
10
10
  BUILD = nil
11
11
  # PRE and BUILD above are modified for dev gems during gem build GHA workflow
12
12
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "datadog/core/worker"
4
+ require "datadog/core/workers/async"
5
+
6
+ # general purpose async worker for CI
7
+ # executes given task once in separate thread
8
+ module Datadog
9
+ module CI
10
+ class Worker < Datadog::Core::Worker
11
+ include Datadog::Core::Workers::Async::Thread
12
+
13
+ DEFAULT_SHUTDOWN_TIMEOUT = 60
14
+ DEFAULT_WAIT_TIMEOUT = 60
15
+
16
+ def stop(timeout = DEFAULT_SHUTDOWN_TIMEOUT)
17
+ join(timeout)
18
+ end
19
+
20
+ def wait_until_done(timeout = DEFAULT_WAIT_TIMEOUT)
21
+ join(timeout)
22
+ end
23
+
24
+ def done?
25
+ started? && !running?
26
+ end
27
+ end
28
+
29
+ class DummyWorker < Worker
30
+ def initialize
31
+ super { nil }
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/datadog/ci.rb CHANGED
@@ -364,6 +364,10 @@ module Datadog
364
364
  def recorder
365
365
  components.ci_recorder
366
366
  end
367
+
368
+ def itr_runner
369
+ components.itr
370
+ end
367
371
  end
368
372
  end
369
373
  end
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.beta1
4
+ version: 1.0.0.beta2
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-03-25 00:00:00.000000000 Z
11
+ date: 2024-04-24 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.beta1
19
+ version: 2.0.0.beta2
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.beta1
26
+ version: 2.0.0.beta2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: msgpack
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -72,6 +72,7 @@ files:
72
72
  - lib/datadog/ci/contrib/cucumber/instrumentation.rb
73
73
  - lib/datadog/ci/contrib/cucumber/integration.rb
74
74
  - lib/datadog/ci/contrib/cucumber/patcher.rb
75
+ - lib/datadog/ci/contrib/cucumber/step.rb
75
76
  - lib/datadog/ci/contrib/integration.rb
76
77
  - lib/datadog/ci/contrib/minitest/configuration/settings.rb
77
78
  - lib/datadog/ci/contrib/minitest/ext.rb
@@ -115,10 +116,18 @@ files:
115
116
  - lib/datadog/ci/ext/settings.rb
116
117
  - lib/datadog/ci/ext/test.rb
117
118
  - lib/datadog/ci/ext/transport.rb
119
+ - lib/datadog/ci/git/local_repository.rb
120
+ - lib/datadog/ci/git/packfiles.rb
121
+ - lib/datadog/ci/git/search_commits.rb
122
+ - lib/datadog/ci/git/tree_uploader.rb
123
+ - lib/datadog/ci/git/upload_packfile.rb
124
+ - lib/datadog/ci/git/user.rb
118
125
  - lib/datadog/ci/itr/coverage/ddcov.rb
119
126
  - lib/datadog/ci/itr/coverage/event.rb
120
127
  - lib/datadog/ci/itr/coverage/transport.rb
128
+ - lib/datadog/ci/itr/coverage/writer.rb
121
129
  - lib/datadog/ci/itr/runner.rb
130
+ - lib/datadog/ci/itr/skippable.rb
122
131
  - lib/datadog/ci/span.rb
123
132
  - lib/datadog/ci/test.rb
124
133
  - lib/datadog/ci/test_module.rb
@@ -149,8 +158,10 @@ files:
149
158
  - lib/datadog/ci/transport/remote_settings_api.rb
150
159
  - lib/datadog/ci/utils/configuration.rb
151
160
  - lib/datadog/ci/utils/git.rb
161
+ - lib/datadog/ci/utils/parsing.rb
152
162
  - lib/datadog/ci/utils/test_run.rb
153
163
  - lib/datadog/ci/version.rb
164
+ - lib/datadog/ci/worker.rb
154
165
  homepage: https://github.com/DataDog/datadog-ci-rb
155
166
  licenses:
156
167
  - BSD-3-Clause