class SimpleCov::Formatter::Codecov

Constants

RECOGNIZED_CIS

CIs

VERSION

Public Instance Methods

build_params(ci) click to toggle source
# File lib/yankedcodecov.rb, line 95
def build_params(ci)
  params = {
    'token' => ENV['CODECOV_TOKEN'],
    'flags' => ENV['CODECOV_FLAG'] || ENV['CODECOV_FLAGS']
  }

  case ci
  when APPVEYOR
    # http://www.appveyor.com/docs/environment-variables
    params[:service] = 'appveyor'
    params[:branch] = ENV['APPVEYOR_REPO_BRANCH']
    params[:build] = ENV['APPVEYOR_JOB_ID']
    params[:pr] = ENV['APPVEYOR_PULL_REQUEST_NUMBER']
    params[:job] = ENV['APPVEYOR_ACCOUNT_NAME'] + '/' + ENV['APPVEYOR_PROJECT_SLUG'] + '/' + ENV['APPVEYOR_BUILD_VERSION']
    params[:slug] = ENV['APPVEYOR_REPO_NAME']
    params[:commit] = ENV['APPVEYOR_REPO_COMMIT']
  when AZUREPIPELINES
    params[:service] = 'azure_pipelines'
    params[:branch] = ENV['BUILD_SOURCEBRANCH']
    params[:pull_request] = ENV['SYSTEM_PULLREQUEST_PULLREQUESTNUMBER']
    params[:job] = ENV['SYSTEM_JOBID']
    params[:build] = ENV['BUILD_BUILDID']
    params[:build_url] = "#{ENV['SYSTEM_TEAMFOUNDATIONSERVERURI']}/#{ENV['SYSTEM_TEAMPROJECT']}/_build/results?buildId=#{ENV['BUILD_BUILDID']}"
    params[:commit] = ENV['BUILD_SOURCEVERSION']
    params[:slug] = ENV['BUILD_REPOSITORY_ID']
  when BITBUCKET
    # https://confluence.atlassian.com/bitbucket/variables-in-pipelines-794502608.html
    params[:service] = 'bitbucket'
    params[:branch] = ENV['BITBUCKET_BRANCH']
    # BITBUCKET_COMMIT does not always provide full commit sha due to a bug https://jira.atlassian.com/browse/BCLOUD-19393#
    params[:commit] = (ENV['BITBUCKET_COMMIT'].length < 40 ? nil : ENV['BITBUCKET_COMMIT'])
    params[:build] = ENV['BITBUCKET_BUILD_NUMBER']
  when BITRISE
    # http://devcenter.bitrise.io/faq/available-environment-variables/
    params[:service] = 'bitrise'
    params[:branch] = ENV['BITRISE_GIT_BRANCH']
    params[:pr] = ENV['BITRISE_PULL_REQUEST']
    params[:build] = ENV['BITRISE_BUILD_NUMBER']
    params[:build_url] = ENV['BITRISE_BUILD_URL']
    params[:commit] = ENV['BITRISE_GIT_COMMIT']
    params[:slug] = ENV['BITRISEIO_GIT_REPOSITORY_OWNER'] + '/' + ENV['BITRISEIO_GIT_REPOSITORY_SLUG']
  when BUILDKITE
    # https://buildkite.com/docs/guides/environment-variables
    params[:service] = 'buildkite'
    params[:branch] = ENV['BUILDKITE_BRANCH']
    params[:build] = ENV['BUILDKITE_BUILD_NUMBER']
    params[:job] = ENV['BUILDKITE_JOB_ID']
    params[:build_url] = ENV['BUILDKITE_BUILD_URL']
    params[:slug] = ENV['BUILDKITE_PROJECT_SLUG']
    params[:commit] = ENV['BUILDKITE_COMMIT']
  when CIRCLE
    # https://circleci.com/docs/environment-variables
    params[:service] = 'circleci'
    params[:build] = ENV['CIRCLE_BUILD_NUM']
    params[:job] = ENV['CIRCLE_NODE_INDEX']
    params[:slug] = if !ENV['CIRCLE_PROJECT_REPONAME'].nil?
                      ENV['CIRCLE_PROJECT_USERNAME'] + '/' + ENV['CIRCLE_PROJECT_REPONAME']
                    else
                      ENV['CIRCLE_REPOSITORY_URL'].gsub(/^.*:/, '').gsub(/\.git$/, '')
                    end
    params[:pr] = ENV['CIRCLE_PR_NUMBER']
    params[:branch] = ENV['CIRCLE_BRANCH']
    params[:commit] = ENV['CIRCLE_SHA1']
  when CODESHIP
    # https://www.codeship.io/documentation/continuous-integration/set-environment-variables/
    params[:service] = 'codeship'
    params[:branch] = ENV['CI_BRANCH']
    params[:commit] = ENV['CI_COMMIT_ID']
    params[:build] = ENV['CI_BUILD_NUMBER']
    params[:build_url] = ENV['CI_BUILD_URL']
  when DRONEIO
    # https://semaphoreapp.com/docs/available-environment-variables.html
    params[:service] = 'drone.io'
    params[:branch] = ENV['DRONE_BRANCH']
    params[:commit] = ENV['DRONE_COMMIT_SHA']
    params[:job] = ENV['DRONE_JOB_NUMBER']
    params[:build] = ENV['DRONE_BUILD_NUMBER']
    params[:build_url] = ENV['DRONE_BUILD_LINK'] || ENV['DRONE_BUILD_URL'] || ENV['CI_BUILD_URL']
    params[:pr] = ENV['DRONE_PULL_REQUEST']
    params[:tag] = ENV['DRONE_TAG']
  when GITLAB
    # http://doc.gitlab.com/ci/examples/README.html#environmental-variables
    # https://gitlab.com/gitlab-org/gitlab-ci-runner/blob/master/lib/build.rb#L96
    # GitLab Runner v9 renamed some environment variables, so we check both old and new variable names.
    params[:service] = 'gitlab'
    params[:branch] = ENV['CI_BUILD_REF_NAME'] || ENV['CI_COMMIT_REF_NAME']
    params[:build] = ENV['CI_BUILD_ID'] || ENV['CI_JOB_ID']
    slug = ENV['CI_BUILD_REPO'] || ENV['CI_REPOSITORY_URL']
    params[:slug] = slug.split('/', 4)[-1].sub('.git', '') if slug
    params[:commit] = ENV['CI_BUILD_REF'] || ENV['CI_COMMIT_SHA']
  when HEROKU
    params[:service] = 'heroku'
    params[:branch] = ENV['HEROKU_TEST_RUN_BRANCH']
    params[:build] = ENV['HEROKU_TEST_RUN_ID']
    params[:commit] = ENV['HEROKU_TEST_RUN_COMMIT_VERSION']
  when JENKINS
    # https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project
    # https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin#GitHubpullrequestbuilderplugin-EnvironmentVariables
    params[:service] = 'jenkins'
    params[:branch] = ENV['ghprbSourceBranch'] || ENV['GIT_BRANCH']
    params[:commit] = ENV['ghprbActualCommit'] || ENV['GIT_COMMIT']
    params[:pr] = ENV['ghprbPullId']
    params[:build] = ENV['BUILD_NUMBER']
    params[:root] = ENV['WORKSPACE']
    params[:build_url] = ENV['BUILD_URL']
  when SEMAPHORE
    # https://semaphoreapp.com/docs/available-environment-variables.html
    params[:service] = 'semaphore'
    params[:branch] = ENV['BRANCH_NAME']
    params[:commit] = ENV['REVISION']
    params[:build] = ENV['SEMAPHORE_BUILD_NUMBER']
    params[:job] = ENV['SEMAPHORE_CURRENT_THREAD']
    params[:slug] = ENV['SEMAPHORE_REPO_SLUG']
  when SHIPPABLE
    # http://docs.shippable.com/en/latest/config.html#common-environment-variables
    params[:service] = 'shippable'
    params[:branch] = ENV['BRANCH']
    params[:build] = ENV['BUILD_NUMBER']
    params[:build_url] = ENV['BUILD_URL']
    params[:pull_request] = ENV['PULL_REQUEST']
    params[:slug] = ENV['REPO_NAME']
    params[:commit] = ENV['COMMIT']
  when SOLANO
    # http://docs.solanolabs.com/Setup/tddium-set-environment-variables/
    params[:service] = 'solano'
    params[:branch] = ENV['TDDIUM_CURRENT_BRANCH']
    params[:commit] = ENV['TDDIUM_CURRENT_COMMIT']
    params[:build] = ENV['TDDIUM_TID']
    params[:pr] = ENV['TDDIUM_PR_ID']
  when TEAMCITY
    # https://confluence.jetbrains.com/display/TCD8/Predefined+Build+Parameters
    # Teamcity does not automatically make build parameters available as environment variables.
    # Add the following environment parameters to the build configuration
    # env.TEAMCITY_BUILD_BRANCH = %teamcity.build.branch%
    # env.TEAMCITY_BUILD_ID = %teamcity.build.id%
    # env.TEAMCITY_BUILD_URL = %teamcity.serverUrl%/viewLog.html?buildId=%teamcity.build.id%
    # env.TEAMCITY_BUILD_COMMIT = %system.build.vcs.number%
    # env.TEAMCITY_BUILD_REPOSITORY = %vcsroot.<YOUR TEAMCITY VCS NAME>.url%
    params[:service] = 'teamcity'
    params[:branch] = ENV['TEAMCITY_BUILD_BRANCH']
    params[:build] = ENV['TEAMCITY_BUILD_ID']
    params[:build_url] = ENV['TEAMCITY_BUILD_URL']
    params[:commit] = ENV['TEAMCITY_BUILD_COMMIT']
    params[:slug] = ENV['TEAMCITY_BUILD_REPOSITORY'].split('/', 4)[-1].sub('.git', '')
  when TRAVIS
    # http://docs.travis-ci.com/user/ci-environment/#Environment-variables
    params[:service] = 'travis'
    params[:branch] = ENV['TRAVIS_BRANCH']
    params[:pull_request] = ENV['TRAVIS_PULL_REQUEST']
    params[:job] = ENV['TRAVIS_JOB_ID']
    params[:slug] = ENV['TRAVIS_REPO_SLUG']
    params[:build] = ENV['TRAVIS_JOB_NUMBER']
    params[:commit] = ENV['TRAVIS_COMMIT']
    params[:env] = ENV['TRAVIS_RUBY_VERSION']
  when WERCKER
    # http://devcenter.wercker.com/articles/steps/variables.html
    params[:service] = 'wercker'
    params[:branch] = ENV['WERCKER_GIT_BRANCH']
    params[:build] = ENV['WERCKER_MAIN_PIPELINE_STARTED']
    params[:slug] = ENV['WERCKER_GIT_OWNER'] + '/' + ENV['WERCKER_GIT_REPOSITORY']
    params[:commit] = ENV['WERCKER_GIT_COMMIT']
  end

  if params[:branch].nil?
    # find branch, commit, repo from git command
    branch = `git rev-parse --abbrev-ref HEAD`.strip
    params[:branch] = branch != 'HEAD' ? branch : 'master'
  end

  if !ENV['VCS_COMMIT_ID'].nil?
    params[:commit] = ENV['VCS_COMMIT_ID']

  elsif params[:commit].nil?
    params[:commit] = `git rev-parse HEAD`.strip
  end

  slug = ENV['CODECOV_SLUG']
  params[:slug] = slug unless slug.nil?

  params[:pr] = params[:pr].sub('#', '') unless params[:pr].nil?

  params
end
create_report(report) click to toggle source
# File lib/yankedcodecov.rb, line 304
def create_report(report)
  result = {
    'meta' => {
      'version' => 'codecov-ruby/v' + SimpleCov::Formatter::Codecov::VERSION
    }
  }
  result.update(result_to_codecov(report))
  result
end
detect_ci() click to toggle source
# File lib/yankedcodecov.rb, line 49
def detect_ci
  ci = if (ENV['CI'] == 'True') && (ENV['APPVEYOR'] == 'True')
         APPVEYOR
       elsif !ENV['TF_BUILD'].nil?
         AZUREPIPELINES
       elsif (ENV['CI'] == 'true') && !ENV['BITBUCKET_BRANCH'].nil?
         BITBUCKET
       elsif (ENV['CI'] == 'true') && (ENV['BITRISE_IO'] == 'true')
         BITRISE
       elsif (ENV['CI'] == 'true') && (ENV['BUILDKITE'] == 'true')
         BUILDKITE
       elsif (ENV['CI'] == 'true') && (ENV['CIRCLECI'] == 'true')
         CIRCLE
       elsif (ENV['CI'] == 'true') && (ENV['CI_NAME'] == 'codeship')
         CODESHIP
       elsif ((ENV['CI'] == 'true') || (ENV['CI'] == 'drone')) && (ENV['DRONE'] == 'true')
         DRONEIO
       elsif !ENV['GITLAB_CI'].nil?
         GITLAB
       elsif ENV['HEROKU_TEST_RUN_ID']
         HEROKU
       elsif !ENV['JENKINS_URL'].nil?
         JENKINS
       elsif (ENV['CI'] == 'true') && (ENV['SEMAPHORE'] == 'true')
         SEMAPHORE
       elsif (ENV['CI'] == 'true') && (ENV['SHIPPABLE'] == 'true')
         SHIPPABLE
       elsif ENV['TDDIUM'] == 'true'
         SOLANO
       elsif ENV['CI_SERVER_NAME'] == 'TeamCity'
         TEAMCITY
       elsif (ENV['CI'] == 'true') && (ENV['TRAVIS'] == 'true')
         TRAVIS
       elsif (ENV['CI'] == 'true') && !ENV['WERCKER_GIT_BRANCH'].nil?
         WERCKER
       end

  if !RECOGNIZED_CIS.include?(ci)
    puts ['x>'.red, 'No CI provider detected.'].join(' ')
  else
    puts "==> #{ci} detected"
  end

  ci
end
display_header() click to toggle source
# File lib/yankedcodecov.rb, line 35
def display_header
  puts [
    '',
    '  _____          _',
    ' / ____|        | |',
    '| |     ___   __| | ___  ___ _____   __',
    '| |    / _ \ / _\`|/ _ \/ __/ _ \ \ / /',
    '| |___| (_) | (_| |  __/ (_| (_) \ V /',
    ' \_____\___/ \__,_|\___|\___\___/ \_/',
    "                               Ruby-#{VERSION}",
    ''
  ].join("\n")
end
format(result) click to toggle source
# File lib/yankedcodecov.rb, line 377
def format(result)
  net_blockers(:off)

  display_header
  ci = detect_ci
  report = create_report(result)
  response = upload_to_codecov(ci, report)
  report['result'] = JSON.parse(response.body)
  handle_report_response(report)
  net_blockers(:on)
  report
end
gzip_report(report) click to toggle source
# File lib/yankedcodecov.rb, line 314
def gzip_report(report)
  puts ['==>'.green, 'Gzipping contents'].join(' ')

  file = Tempfile.new
  Zlib::GzipWriter.open(file.path) do |gz|
    gz.write report
  end
  file.rewind
  gzipped_report = file.read
  file.close

  gzipped_report
end
handle_report_response(report) click to toggle source
# File lib/yankedcodecov.rb, line 369
def handle_report_response(report)
  if report['result']['uploaded']
    puts "    View reports at #{report['result']['url']}"
  else
    puts '    X> Failed to upload coverage reports'.red
  end
end
retry_request(req, https) click to toggle source
# File lib/yankedcodecov.rb, line 279
def retry_request(req, https)
  retries = 3
  begin
    response = https.request(req)
  rescue Timeout::Error => e
    retries -= 1

    if retries.zero?
      puts 'Timeout error uploading coverage reports to Codecov. Out of retries.'
      puts e
      return response
    end

    puts 'Timeout error uploading coverage reports to Codecov. Retrying...'
    puts e
    retry
  rescue StandardError => e
    puts 'Error uploading coverage reports to Codecov. Sorry'
    puts e
    return response
  end

  response
end
upload_to_codecov(ci, report) click to toggle source
# File lib/yankedcodecov.rb, line 328
def upload_to_codecov(ci, report)
  url = ENV['CODECOV_URL'] || 'https://codecov.io'

  params = build_params(ci)
  params_secret_token = params.clone
  params_secret_token['token'] = 'secret'

  query = URI.encode_www_form(params)
  query_without_token = URI.encode_www_form(params_secret_token)

  gzipped_report = gzip_report(report)

  report['params'] = params
  report['query'] = query

  puts ['==>'.green, 'Uploading reports'].join(' ')
  puts "    url:   #{url}"
  puts "    query: #{query_without_token}"

  upload_to_v2(url, gzipped_report, query, query_without_token)
end
upload_to_v2(url, report, query, query_without_token) click to toggle source
# File lib/yankedcodecov.rb, line 350
def upload_to_v2(url, report, query, query_without_token)
  uri = URI.parse(url.chomp('/') + '/upload/v2')
  https = Net::HTTP.new(uri.host, uri.port)
  https.use_ssl = !url.match(/^https/).nil?

  puts ['-> '.green, 'Uploading to Codecov'].join(' ')
  puts "#{url}/#{uri.path}?#{query_without_token}"

  req = Net::HTTP::Post.new(
    "#{uri.path}?#{query}",
    {
      'X-Reduced-Redundancy' => 'false',
      'Content-Type' => 'text/plain'
    }
  )
  req.body = report
  retry_request(req, https)
end

Private Instance Methods

file_to_codecov(file) click to toggle source

Format coverage data for a single file for the Codecov.io API.

@param file [SimpleCov::SourceFile] The file to process. @return [Array<nil, Integer>]

# File lib/yankedcodecov.rb, line 429
def file_to_codecov(file)
  # Initial nil is required to offset line numbers.
  [nil] + file.lines.map do |line|
    if line.skipped?
      nil
    else
      line.coverage
    end
  end
end
net_blockers(switch) click to toggle source

Toggle VCR and WebMock on or off

@param switch Toggle switch for Net Blockers. @return [Boolean]

# File lib/yankedcodecov.rb, line 453
def net_blockers(switch)
  throw 'Only :on or :off' unless %i[on off].include? switch

  if defined?(VCR)
    case switch
    when :on
      VCR.turn_on!
    when :off
      VCR.turn_off!(ignore_cassettes: true)
    end
  end

  if defined?(WebMock)
    # WebMock on by default
    # VCR depends on WebMock 1.8.11; no method to check whether enabled.
    case switch
    when :on
      WebMock.enable!
    when :off
      WebMock.disable!
    end
  end

  true
end
result_to_codecov(result) click to toggle source

Format SimpleCov coverage data for the Codecov.io API.

@param result [SimpleCov::Result] The coverage data to process. @return [Hash]

# File lib/yankedcodecov.rb, line 396
def result_to_codecov(result)
  {
    'coverage' => result_to_codecov_coverage(result),
    'messages' => result_to_codecov_messages(result)
  }
end
result_to_codecov_coverage(result) click to toggle source

Format SimpleCov coverage data for the Codecov.io coverage API.

@param result [SimpleCov::Result] The coverage data to process. @return [Hash<String, Array>]

# File lib/yankedcodecov.rb, line 407
def result_to_codecov_coverage(result)
  result.files.each_with_object({}) do |file, memo|
    memo[shortened_filename(file)] = file_to_codecov(file)
  end
end
result_to_codecov_messages(result) click to toggle source

Format SimpleCov coverage data for the Codecov.io messages API.

@param result [SimpleCov::Result] The coverage data to process. @return [Hash<String, Hash>]

# File lib/yankedcodecov.rb, line 417
def result_to_codecov_messages(result)
  result.files.each_with_object({}) do |file, memo|
    memo[shortened_filename(file)] = file.lines.each_with_object({}) do |line, lines_memo|
      lines_memo[line.line_number.to_s] = 'skipped' if line.skipped?
    end
  end
end
shortened_filename(file) click to toggle source

Get a filename relative to the project root. Based on github.com/colszowka/simplecov-html, copyright Christoph Olszowka.

@param file [SimeplCov::SourceFile] The file to use. @return [String]

# File lib/yankedcodecov.rb, line 445
def shortened_filename(file)
  file.filename.gsub(/^#{SimpleCov.root}/, '.').gsub(%r{^\./}, '')
end