codecov 0.3.0 → 0.5.0

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