codecov 0.3.0 → 0.5.0

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