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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +127 -0
- data/LICENSE +21 -0
- data/README.md +111 -0
- data/lib/codecov.rb +4 -613
- data/lib/codecov/formatter.rb +126 -0
- data/lib/codecov/uploader.rb +546 -0
- data/lib/codecov/version.rb +1 -1
- metadata +13 -3
@@ -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
|