codecov 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/lib/codecov.rb +620 -0
  3. data/test/test_codecov.rb +675 -0
  4. metadata +158 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4e9408049c526c9e3733a4b4431fb8f5f479db5a6d70ac769655c178690b878c
4
+ data.tar.gz: 7f73f2059b9b0f1e8f5549e955f8482532ef34c8505114f068ba689042d6ada9
5
+ SHA512:
6
+ metadata.gz: 0332e23b36a550b36f86dab1d9915c616dca2a119735616486ac4829ad88ad05c46721de6bfea2c400b3f735b26167587acda24db84397842bf9328616fe2997
7
+ data.tar.gz: e9d9b9ecd83b6c4037d6ca15964184fbe9745e93f33f0b7a17aa0e4d032d13353ffb1893288240c9c004b353e97623f2d88e4e5b3371b507050a8f9273eb5a68
@@ -0,0 +1,620 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'json'
5
+ require 'net/http'
6
+ require 'simplecov'
7
+ require 'zlib'
8
+
9
+ class SimpleCov::Formatter::Codecov
10
+ VERSION = '0.1.21'
11
+
12
+ ### CIs
13
+ RECOGNIZED_CIS = [
14
+ APPVEYOR = 'Appveyor CI',
15
+ AZUREPIPELINES = 'Azure Pipelines',
16
+ BITBUCKET = 'Bitbucket',
17
+ BITRISE = 'Bitrise CI',
18
+ BUILDKITE = 'Buildkite CI',
19
+ CIRCLE = 'Circle CI',
20
+ CODEBUILD = 'Codebuild CI',
21
+ CODESHIP = 'Codeship CI',
22
+ DRONEIO = 'Drone CI',
23
+ GITHUB = 'GitHub Actions',
24
+ GITLAB = 'GitLab CI',
25
+ HEROKU = 'Heroku CI',
26
+ JENKINS = 'Jenkins CI',
27
+ SEMAPHORE = 'Semaphore CI',
28
+ SHIPPABLE = 'Shippable',
29
+ SOLANO = 'Solano CI',
30
+ TEAMCITY = 'TeamCity CI',
31
+ TRAVIS = 'Travis CI',
32
+ WERCKER = 'Wercker CI'
33
+ ].freeze
34
+
35
+ def display_header
36
+ puts [
37
+ '',
38
+ ' _____ _',
39
+ ' / ____| | |',
40
+ '| | ___ __| | ___ ___ _____ __',
41
+ '| | / _ \ / _\`|/ _ \/ __/ _ \ \ / /',
42
+ '| |___| (_) | (_| | __/ (_| (_) \ V /',
43
+ ' \_____\___/ \__,_|\___|\___\___/ \_/',
44
+ " Ruby-#{VERSION}",
45
+ ''
46
+ ].join("\n")
47
+ end
48
+
49
+ def detect_ci
50
+ ci = if (ENV['CI'] == 'True') && (ENV['APPVEYOR'] == 'True')
51
+ APPVEYOR
52
+ elsif !ENV['TF_BUILD'].nil?
53
+ AZUREPIPELINES
54
+ elsif (ENV['CI'] == 'true') && !ENV['BITBUCKET_BRANCH'].nil?
55
+ BITBUCKET
56
+ elsif (ENV['CI'] == 'true') && (ENV['BITRISE_IO'] == 'true')
57
+ BITRISE
58
+ elsif (ENV['CI'] == 'true') && (ENV['BUILDKITE'] == 'true')
59
+ BUILDKITE
60
+ elsif (ENV['CI'] == 'true') && (ENV['CIRCLECI'] == 'true')
61
+ CIRCLE
62
+ elsif ENV['CODEBUILD_CI'] == 'true'
63
+ CODEBUILD
64
+ elsif (ENV['CI'] == 'true') && (ENV['CI_NAME'] == 'codeship')
65
+ CODESHIP
66
+ elsif ((ENV['CI'] == 'true') || (ENV['CI'] == 'drone')) && (ENV['DRONE'] == 'true')
67
+ DRONEIO
68
+ elsif (ENV['CI'] == 'true') && (ENV['GITHUB_ACTIONS'] == 'true')
69
+ GITHUB
70
+ elsif !ENV['GITLAB_CI'].nil?
71
+ GITLAB
72
+ elsif ENV['HEROKU_TEST_RUN_ID']
73
+ HEROKU
74
+ elsif !ENV['JENKINS_URL'].nil?
75
+ JENKINS
76
+ elsif (ENV['CI'] == 'true') && (ENV['SEMAPHORE'] == 'true')
77
+ SEMAPHORE
78
+ elsif (ENV['CI'] == 'true') && (ENV['SHIPPABLE'] == 'true')
79
+ SHIPPABLE
80
+ elsif ENV['TDDIUM'] == 'true'
81
+ SOLANO
82
+ elsif ENV['CI_SERVER_NAME'] == 'TeamCity'
83
+ TEAMCITY
84
+ elsif (ENV['CI'] == 'true') && (ENV['TRAVIS'] == 'true')
85
+ TRAVIS
86
+ elsif (ENV['CI'] == 'true') && !ENV['WERCKER_GIT_BRANCH'].nil?
87
+ WERCKER
88
+ end
89
+
90
+ if !RECOGNIZED_CIS.include?(ci)
91
+ puts ['x>'.red, 'No CI provider detected.'].join(' ')
92
+ else
93
+ puts "==> #{ci} detected"
94
+ end
95
+
96
+ ci
97
+ end
98
+
99
+ def build_params(ci)
100
+ params = {
101
+ 'token' => ENV['CODECOV_TOKEN'],
102
+ 'flags' => ENV['CODECOV_FLAG'] || ENV['CODECOV_FLAGS'],
103
+ 'package' => "ruby-#{VERSION}"
104
+ }
105
+
106
+ case ci
107
+ when APPVEYOR
108
+ # http://www.appveyor.com/docs/environment-variables
109
+ params[:service] = 'appveyor'
110
+ params[:branch] = ENV['APPVEYOR_REPO_BRANCH']
111
+ params[:build] = ENV['APPVEYOR_JOB_ID']
112
+ params[:pr] = ENV['APPVEYOR_PULL_REQUEST_NUMBER']
113
+ params[:job] = ENV['APPVEYOR_ACCOUNT_NAME'] + '/' + ENV['APPVEYOR_PROJECT_SLUG'] + '/' + ENV['APPVEYOR_BUILD_VERSION']
114
+ params[:slug] = ENV['APPVEYOR_REPO_NAME']
115
+ params[:commit] = ENV['APPVEYOR_REPO_COMMIT']
116
+ when AZUREPIPELINES
117
+ params[:service] = 'azure_pipelines'
118
+ params[:branch] = ENV['BUILD_SOURCEBRANCH']
119
+ params[:pull_request] = ENV['SYSTEM_PULLREQUEST_PULLREQUESTNUMBER']
120
+ params[:job] = ENV['SYSTEM_JOBID']
121
+ params[:build] = ENV['BUILD_BUILDID']
122
+ params[:build_url] = "#{ENV['SYSTEM_TEAMFOUNDATIONSERVERURI']}/#{ENV['SYSTEM_TEAMPROJECT']}/_build/results?buildId=#{ENV['BUILD_BUILDID']}"
123
+ params[:commit] = ENV['BUILD_SOURCEVERSION']
124
+ params[:slug] = ENV['BUILD_REPOSITORY_ID']
125
+ when BITBUCKET
126
+ # https://confluence.atlassian.com/bitbucket/variables-in-pipelines-794502608.html
127
+ params[:service] = 'bitbucket'
128
+ params[:branch] = ENV['BITBUCKET_BRANCH']
129
+ # BITBUCKET_COMMIT does not always provide full commit sha due to a bug https://jira.atlassian.com/browse/BCLOUD-19393#
130
+ params[:commit] = (ENV['BITBUCKET_COMMIT'].length < 40 ? nil : ENV['BITBUCKET_COMMIT'])
131
+ params[:build] = ENV['BITBUCKET_BUILD_NUMBER']
132
+ when BITRISE
133
+ # http://devcenter.bitrise.io/faq/available-environment-variables/
134
+ params[:service] = 'bitrise'
135
+ params[:branch] = ENV['BITRISE_GIT_BRANCH']
136
+ params[:pr] = ENV['BITRISE_PULL_REQUEST']
137
+ params[:build] = ENV['BITRISE_BUILD_NUMBER']
138
+ params[:build_url] = ENV['BITRISE_BUILD_URL']
139
+ params[:commit] = ENV['BITRISE_GIT_COMMIT']
140
+ params[:slug] = ENV['BITRISEIO_GIT_REPOSITORY_OWNER'] + '/' + ENV['BITRISEIO_GIT_REPOSITORY_SLUG']
141
+ when BUILDKITE
142
+ # https://buildkite.com/docs/guides/environment-variables
143
+ params[:service] = 'buildkite'
144
+ params[:branch] = ENV['BUILDKITE_BRANCH']
145
+ params[:build] = ENV['BUILDKITE_BUILD_NUMBER']
146
+ params[:job] = ENV['BUILDKITE_JOB_ID']
147
+ params[:build_url] = ENV['BUILDKITE_BUILD_URL']
148
+ params[:slug] = ENV['BUILDKITE_PROJECT_SLUG']
149
+ params[:commit] = ENV['BUILDKITE_COMMIT']
150
+ when CIRCLE
151
+ # https://circleci.com/docs/environment-variables
152
+ params[:service] = 'circleci'
153
+ params[:build] = ENV['CIRCLE_BUILD_NUM']
154
+ params[:job] = ENV['CIRCLE_NODE_INDEX']
155
+ params[:slug] = if !ENV['CIRCLE_PROJECT_REPONAME'].nil?
156
+ ENV['CIRCLE_PROJECT_USERNAME'] + '/' + ENV['CIRCLE_PROJECT_REPONAME']
157
+ else
158
+ ENV['CIRCLE_REPOSITORY_URL'].gsub(/^.*:/, '').gsub(/\.git$/, '')
159
+ end
160
+ params[:pr] = ENV['CIRCLE_PR_NUMBER']
161
+ params[:branch] = ENV['CIRCLE_BRANCH']
162
+ params[:commit] = ENV['CIRCLE_SHA1']
163
+ when CODEBUILD
164
+ # https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html
165
+ params[:service] = 'codebuild'
166
+ params[:branch] = ENV['CODEBUILD_WEBHOOK_HEAD_REF'].split('/')[2]
167
+ params[:build] = ENV['CODEBUILD_BUILD_ID']
168
+ params[:commit] = ENV['CODEBUILD_RESOLVED_SOURCE_VERSION']
169
+ params[:job] = ENV['CODEBUILD_BUILD_ID']
170
+ params[:slug] = ENV['CODEBUILD_SOURCE_REPO_URL'].match(/.*github.com\/(?<slug>.*).git/)['slug']
171
+ params[:pr] = ENV['CODEBUILD_SOURCE_VERSION'].match(/pr\/(?<pr>.*)/)['pr'] if ENV['CODEBUILD_SOURCE_VERSION']
172
+ when CODESHIP
173
+ # https://www.codeship.io/documentation/continuous-integration/set-environment-variables/
174
+ params[:service] = 'codeship'
175
+ params[:branch] = ENV['CI_BRANCH']
176
+ params[:commit] = ENV['CI_COMMIT_ID']
177
+ params[:build] = ENV['CI_BUILD_NUMBER']
178
+ params[:build_url] = ENV['CI_BUILD_URL']
179
+ when DRONEIO
180
+ # https://semaphoreapp.com/docs/available-environment-variables.html
181
+ params[:service] = 'drone.io'
182
+ params[:branch] = ENV['DRONE_BRANCH']
183
+ params[:commit] = ENV['DRONE_COMMIT_SHA']
184
+ params[:job] = ENV['DRONE_JOB_NUMBER']
185
+ params[:build] = ENV['DRONE_BUILD_NUMBER']
186
+ params[:build_url] = ENV['DRONE_BUILD_LINK'] || ENV['DRONE_BUILD_URL'] || ENV['CI_BUILD_URL']
187
+ params[:pr] = ENV['DRONE_PULL_REQUEST']
188
+ params[:tag] = ENV['DRONE_TAG']
189
+ when GITHUB
190
+ # https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
191
+ params[:service] = 'github-actions'
192
+ params[:branch] = ENV['GITHUB_HEAD_REF'] || ENV['GITHUB_REF'].sub('refs/head/', '')
193
+ # PR refs are in the format: refs/pull/7/merge for pull_request events
194
+ params[:pr] = ENV['GITHUB_REF'].split('/')[2] unless ENV['GITHUB_HEAD_REF'].nil? || ENV['GITHUB_HEAD_REF'].empty?
195
+ params[:slug] = ENV['GITHUB_REPOSITORY']
196
+ params[:build] = ENV['GITHUB_RUN_ID']
197
+ params[:commit] = ENV['GITHUB_SHA']
198
+ when GITLAB
199
+ # http://doc.gitlab.com/ci/examples/README.html#environmental-variables
200
+ # https://gitlab.com/gitlab-org/gitlab-ci-runner/blob/master/lib/build.rb#L96
201
+ # GitLab Runner v9 renamed some environment variables, so we check both old and new variable names.
202
+ params[:service] = 'gitlab'
203
+ params[:branch] = ENV['CI_BUILD_REF_NAME'] || ENV['CI_COMMIT_REF_NAME']
204
+ params[:build] = ENV['CI_BUILD_ID'] || ENV['CI_JOB_ID']
205
+ slug = ENV['CI_BUILD_REPO'] || ENV['CI_REPOSITORY_URL']
206
+ params[:slug] = slug.split('/', 4)[-1].sub('.git', '') if slug
207
+ params[:commit] = ENV['CI_BUILD_REF'] || ENV['CI_COMMIT_SHA']
208
+ when HEROKU
209
+ params[:service] = 'heroku'
210
+ params[:branch] = ENV['HEROKU_TEST_RUN_BRANCH']
211
+ params[:build] = ENV['HEROKU_TEST_RUN_ID']
212
+ params[:commit] = ENV['HEROKU_TEST_RUN_COMMIT_VERSION']
213
+ when JENKINS
214
+ # https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project
215
+ # https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin#GitHubpullrequestbuilderplugin-EnvironmentVariables
216
+ params[:service] = 'jenkins'
217
+ params[:branch] = ENV['ghprbSourceBranch'] || ENV['GIT_BRANCH']
218
+ params[:commit] = ENV['ghprbActualCommit'] || ENV['GIT_COMMIT']
219
+ params[:pr] = ENV['ghprbPullId']
220
+ params[:build] = ENV['BUILD_NUMBER']
221
+ params[:root] = ENV['WORKSPACE']
222
+ params[:build_url] = ENV['BUILD_URL']
223
+ when SEMAPHORE
224
+ # https://semaphoreapp.com/docs/available-environment-variables.html
225
+ params[:service] = 'semaphore'
226
+ params[:branch] = ENV['BRANCH_NAME']
227
+ params[:commit] = ENV['REVISION']
228
+ params[:build] = ENV['SEMAPHORE_BUILD_NUMBER']
229
+ params[:job] = ENV['SEMAPHORE_CURRENT_THREAD']
230
+ params[:slug] = ENV['SEMAPHORE_REPO_SLUG']
231
+ when SHIPPABLE
232
+ # http://docs.shippable.com/en/latest/config.html#common-environment-variables
233
+ params[:service] = 'shippable'
234
+ params[:branch] = ENV['BRANCH']
235
+ params[:build] = ENV['BUILD_NUMBER']
236
+ params[:build_url] = ENV['BUILD_URL']
237
+ params[:pull_request] = ENV['PULL_REQUEST']
238
+ params[:slug] = ENV['REPO_NAME']
239
+ params[:commit] = ENV['COMMIT']
240
+ when SOLANO
241
+ # http://docs.solanolabs.com/Setup/tddium-set-environment-variables/
242
+ params[:service] = 'solano'
243
+ params[:branch] = ENV['TDDIUM_CURRENT_BRANCH']
244
+ params[:commit] = ENV['TDDIUM_CURRENT_COMMIT']
245
+ params[:build] = ENV['TDDIUM_TID']
246
+ params[:pr] = ENV['TDDIUM_PR_ID']
247
+ when TEAMCITY
248
+ # https://confluence.jetbrains.com/display/TCD8/Predefined+Build+Parameters
249
+ # Teamcity does not automatically make build parameters available as environment variables.
250
+ # Add the following environment parameters to the build configuration
251
+ # env.TEAMCITY_BUILD_BRANCH = %teamcity.build.branch%
252
+ # env.TEAMCITY_BUILD_ID = %teamcity.build.id%
253
+ # env.TEAMCITY_BUILD_URL = %teamcity.serverUrl%/viewLog.html?buildId=%teamcity.build.id%
254
+ # env.TEAMCITY_BUILD_COMMIT = %system.build.vcs.number%
255
+ # env.TEAMCITY_BUILD_REPOSITORY = %vcsroot.<YOUR TEAMCITY VCS NAME>.url%
256
+ params[:service] = 'teamcity'
257
+ params[:branch] = ENV['TEAMCITY_BUILD_BRANCH']
258
+ params[:build] = ENV['TEAMCITY_BUILD_ID']
259
+ params[:build_url] = ENV['TEAMCITY_BUILD_URL']
260
+ params[:commit] = ENV['TEAMCITY_BUILD_COMMIT']
261
+ params[:slug] = ENV['TEAMCITY_BUILD_REPOSITORY'].split('/', 4)[-1].sub('.git', '')
262
+ when TRAVIS
263
+ # http://docs.travis-ci.com/user/ci-environment/#Environment-variables
264
+ params[:service] = 'travis'
265
+ params[:branch] = ENV['TRAVIS_BRANCH']
266
+ params[:pull_request] = ENV['TRAVIS_PULL_REQUEST']
267
+ params[:job] = ENV['TRAVIS_JOB_ID']
268
+ params[:slug] = ENV['TRAVIS_REPO_SLUG']
269
+ params[:build] = ENV['TRAVIS_JOB_NUMBER']
270
+ params[:commit] = ENV['TRAVIS_COMMIT']
271
+ params[:env] = ENV['TRAVIS_RUBY_VERSION']
272
+ when WERCKER
273
+ # http://devcenter.wercker.com/articles/steps/variables.html
274
+ params[:service] = 'wercker'
275
+ params[:branch] = ENV['WERCKER_GIT_BRANCH']
276
+ params[:build] = ENV['WERCKER_MAIN_PIPELINE_STARTED']
277
+ params[:slug] = ENV['WERCKER_GIT_OWNER'] + '/' + ENV['WERCKER_GIT_REPOSITORY']
278
+ params[:commit] = ENV['WERCKER_GIT_COMMIT']
279
+ end
280
+
281
+ if params[:branch].nil?
282
+ # find branch, commit, repo from git command
283
+ branch = `git rev-parse --abbrev-ref HEAD`.strip
284
+ params[:branch] = branch != 'HEAD' ? branch : 'master'
285
+ end
286
+
287
+ if !ENV['VCS_COMMIT_ID'].nil?
288
+ params[:commit] = ENV['VCS_COMMIT_ID']
289
+
290
+ elsif params[:commit].nil?
291
+ params[:commit] = `git rev-parse HEAD`.strip
292
+ end
293
+
294
+ slug = ENV['CODECOV_SLUG']
295
+ params[:slug] = slug unless slug.nil?
296
+
297
+ params[:pr] = params[:pr].sub('#', '') unless params[:pr].nil?
298
+
299
+ params
300
+ end
301
+
302
+ def retry_request(req, https)
303
+ retries = 3
304
+ begin
305
+ response = https.request(req)
306
+ rescue Timeout::Error, SocketError => e
307
+ retries -= 1
308
+
309
+ if retries.zero?
310
+ puts 'Timeout or connection error uploading coverage reports to Codecov. Out of retries.'
311
+ puts e
312
+ return response
313
+ end
314
+
315
+ puts 'Timeout or connection error uploading coverage reports to Codecov. Retrying...'
316
+ puts e
317
+ retry
318
+ rescue StandardError => e
319
+ puts 'Error uploading coverage reports to Codecov. Sorry'
320
+ puts e.class.name
321
+ puts e
322
+ return response
323
+ end
324
+
325
+ response
326
+ end
327
+
328
+ def create_report(report)
329
+ result = {
330
+ 'meta' => {
331
+ 'version' => 'codecov-ruby/v' + VERSION
332
+ }
333
+ }
334
+ result.update(result_to_codecov(report))
335
+ result
336
+ end
337
+
338
+ def gzip_report(report)
339
+ puts ['==>'.green, 'Gzipping contents'].join(' ')
340
+
341
+ io = StringIO.new
342
+ gzip = Zlib::GzipWriter.new(io)
343
+ gzip << report
344
+ gzip.close
345
+
346
+ io.string
347
+ end
348
+
349
+ def upload_to_codecov(ci, report)
350
+ url = ENV['CODECOV_URL'] || 'https://codecov.io'
351
+ is_enterprise = url != 'https://codecov.io'
352
+
353
+ params = build_params(ci)
354
+ params_secret_token = params.clone
355
+ params_secret_token['token'] = 'secret'
356
+
357
+ query = URI.encode_www_form(params)
358
+ query_without_token = URI.encode_www_form(params_secret_token)
359
+
360
+ gzipped_report = gzip_report(report['codecov'])
361
+
362
+ report['params'] = params
363
+ report['query'] = query
364
+
365
+ puts ['==>'.green, 'Uploading reports'].join(' ')
366
+ puts " url: #{url}"
367
+ puts " query: #{query_without_token}"
368
+
369
+ response = false
370
+ unless is_enterprise
371
+ response = upload_to_v4(url, gzipped_report, query, query_without_token)
372
+ return false if response == false
373
+ end
374
+
375
+ response || upload_to_v2(url, gzipped_report, query, query_without_token)
376
+ end
377
+
378
+ def upload_to_v4(url, report, query, query_without_token)
379
+ uri = URI.parse(url.chomp('/') + '/upload/v4')
380
+ https = Net::HTTP.new(uri.host, uri.port)
381
+ https.use_ssl = !url.match(/^https/).nil?
382
+
383
+ puts ['-> '.green, 'Pinging Codecov'].join(' ')
384
+ puts "#{url}#{uri.path}?#{query_without_token}"
385
+
386
+ req = Net::HTTP::Post.new(
387
+ "#{uri.path}?#{query}",
388
+ {
389
+ 'X-Reduced-Redundancy' => 'false',
390
+ 'X-Content-Encoding' => 'application/x-gzip',
391
+ 'Content-Type' => 'text/plain'
392
+ }
393
+ )
394
+ response = retry_request(req, https)
395
+ if !response&.code || response.code == '400'
396
+ puts response&.body&.red
397
+ return false
398
+ end
399
+
400
+ reports_url = response.body.lines[0]
401
+ s3target = response.body.lines[1]
402
+ puts ['-> '.green, 'Uploading to'].join(' ')
403
+ puts s3target
404
+
405
+ uri = URI(s3target)
406
+ https = Net::HTTP.new(uri.host, uri.port)
407
+ https.use_ssl = true
408
+ req = Net::HTTP::Put.new(
409
+ s3target,
410
+ {
411
+ 'Content-Encoding' => 'gzip',
412
+ 'Content-Type' => 'text/plain'
413
+ }
414
+ )
415
+ req.body = report
416
+ res = retry_request(req, https)
417
+ if res.body == ''
418
+ {
419
+ 'uploaded' => true,
420
+ 'url' => reports_url,
421
+ 'meta' => {
422
+ 'status' => res.code
423
+ },
424
+ 'message' => 'Coverage reports upload successfully'
425
+ }.to_json
426
+ else
427
+ puts ['-> '.black, 'Could not upload reports via v4 API, defaulting to v2'].join(' ')
428
+ puts res.body.red
429
+ nil
430
+ end
431
+ end
432
+
433
+ def upload_to_v2(url, report, query, query_without_token)
434
+ uri = URI.parse(url.chomp('/') + '/upload/v2')
435
+ https = Net::HTTP.new(uri.host, uri.port)
436
+ https.use_ssl = !url.match(/^https/).nil?
437
+
438
+ puts ['-> '.green, 'Uploading to Codecov'].join(' ')
439
+ puts "#{url}#{uri.path}?#{query_without_token}"
440
+
441
+ req = Net::HTTP::Post.new(
442
+ "#{uri.path}?#{query}",
443
+ {
444
+ 'Accept' => 'application/json',
445
+ 'Content-Encoding' => 'gzip',
446
+ 'Content-Type' => 'text/plain',
447
+ 'X-Content-Encoding' => 'gzip'
448
+ }
449
+ )
450
+ req.body = report
451
+ res = retry_request(req, https)
452
+ res&.body
453
+ end
454
+
455
+ def handle_report_response(report)
456
+ if report['result']['uploaded']
457
+ puts " View reports at #{report['result']['url']}"
458
+ else
459
+ puts ' X> Failed to upload coverage reports'.red
460
+ end
461
+ end
462
+
463
+ def format(result, disable_net_blockers = true)
464
+ net_blockers(:off) if disable_net_blockers
465
+
466
+ display_header
467
+ ci = detect_ci
468
+ report = create_report(result)
469
+ response = upload_to_codecov(ci, report)
470
+ if response == false
471
+ report['result'] = { 'uploaded' => false }
472
+ return report
473
+ end
474
+
475
+ report['result'] = JSON.parse(response)
476
+ handle_report_response(report)
477
+
478
+ net_blockers(:on) if disable_net_blockers
479
+ report
480
+ end
481
+
482
+ private
483
+
484
+ # Format SimpleCov coverage data for the Codecov.io API.
485
+ #
486
+ # @param result [SimpleCov::Result] The coverage data to process.
487
+ # @return [Hash]
488
+ def result_to_codecov(result)
489
+ {
490
+ 'codecov' => result_to_codecov_report(result),
491
+ 'coverage' => result_to_codecov_coverage(result),
492
+ 'messages' => result_to_codecov_messages(result)
493
+ }
494
+ end
495
+
496
+ def result_to_codecov_report(result)
497
+ report = file_network.join("\n").concat("\n")
498
+ report = report.concat({ 'coverage' => result_to_codecov_coverage(result) }.to_json)
499
+ report
500
+ end
501
+
502
+ def file_network
503
+ invalid_file_types = [
504
+ 'woff', 'eot', 'otf', # fonts
505
+ 'gif', 'png', 'jpg', 'jpeg', 'psd', # images
506
+ 'ptt', 'pptx', 'numbers', 'pages', 'md', 'txt', 'xlsx', 'docx', 'doc', 'pdf', 'csv', # docs
507
+ 'yml', 'yaml', '.gitignore'
508
+ ].freeze
509
+
510
+ invalid_directories = [
511
+ 'node_modules/'
512
+ ]
513
+
514
+ puts ['==>'.green, 'Appending file network'].join(' ')
515
+ network = []
516
+ Dir['**/*'].keep_if do |file|
517
+ if File.file?(file) && !file.end_with?(*invalid_file_types) && !file.include?(*invalid_directories)
518
+ network.push(file)
519
+ end
520
+ end
521
+ network.push('<<<<<< network')
522
+ network
523
+ end
524
+
525
+ # Format SimpleCov coverage data for the Codecov.io coverage API.
526
+ #
527
+ # @param result [SimpleCov::Result] The coverage data to process.
528
+ # @return [Hash<String, Array>]
529
+ def result_to_codecov_coverage(result)
530
+ result.files.each_with_object({}) do |file, memo|
531
+ memo[shortened_filename(file)] = file_to_codecov(file)
532
+ end
533
+ end
534
+
535
+ # Format SimpleCov coverage data for the Codecov.io messages API.
536
+ #
537
+ # @param result [SimpleCov::Result] The coverage data to process.
538
+ # @return [Hash<String, Hash>]
539
+ def result_to_codecov_messages(result)
540
+ result.files.each_with_object({}) do |file, memo|
541
+ memo[shortened_filename(file)] = file.lines.each_with_object({}) do |line, lines_memo|
542
+ lines_memo[line.line_number.to_s] = 'skipped' if line.skipped?
543
+ end
544
+ end
545
+ end
546
+
547
+ # Format coverage data for a single file for the Codecov.io API.
548
+ #
549
+ # @param file [SimpleCov::SourceFile] The file to process.
550
+ # @return [Array<nil, Integer>]
551
+ def file_to_codecov(file)
552
+ # Initial nil is required to offset line numbers.
553
+ [nil] + file.lines.map do |line|
554
+ if line.skipped?
555
+ nil
556
+ else
557
+ line.coverage
558
+ end
559
+ end
560
+ end
561
+
562
+ # Get a filename relative to the project root. Based on
563
+ # https://github.com/colszowka/simplecov-html, copyright Christoph Olszowka.
564
+ #
565
+ # @param file [SimeplCov::SourceFile] The file to use.
566
+ # @return [String]
567
+ def shortened_filename(file)
568
+ file.filename.gsub(/^#{SimpleCov.root}/, '.').gsub(%r{^\./}, '')
569
+ end
570
+
571
+ # Toggle VCR and WebMock on or off
572
+ #
573
+ # @param switch Toggle switch for Net Blockers.
574
+ # @return [Boolean]
575
+ def net_blockers(switch)
576
+ throw 'Only :on or :off' unless %i[on off].include? switch
577
+
578
+ if defined?(VCR)
579
+ case switch
580
+ when :on
581
+ VCR.turn_on!
582
+ when :off
583
+ VCR.turn_off!(ignore_cassettes: true)
584
+ end
585
+ end
586
+
587
+ if defined?(WebMock)
588
+ # WebMock on by default
589
+ # VCR depends on WebMock 1.8.11; no method to check whether enabled.
590
+ case switch
591
+ when :on
592
+ WebMock.enable!
593
+ when :off
594
+ WebMock.disable!
595
+ end
596
+ end
597
+
598
+ true
599
+ end
600
+ end
601
+
602
+ # https://stackoverflow.com/a/11482430/5769383
603
+ class String
604
+ # colorization
605
+ def colorize(color_code)
606
+ "\e[#{color_code}m#{self}\e[0m"
607
+ end
608
+
609
+ def black
610
+ colorize(30)
611
+ end
612
+
613
+ def red
614
+ colorize(31)
615
+ end
616
+
617
+ def green
618
+ colorize(32)
619
+ end
620
+ end