codecov 0.1.21

Sign up to get free protection for your applications and to get access to all the features.
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