codeclimate/test-coverage-report

View on GitHub
lib/cc/test_coverage_report.rb

Summary

Maintainability
A
25 mins
Test Coverage
# frozen_string_literal: true
 
module CC
class TestCoverageReport
File = Struct.new(:path, :lines)
Line = Struct.new(:number, :covered)
NoTestReportFound = Class.new(StandardError)
 
def initialize(repo_slug:, cc_access_token:, github_access_token:, days_since:)
@repo_slug = repo_slug
@cc_access_token = cc_access_token
@github_access_token = github_access_token
@days_since = days_since
end
 
def find_test_report(path)
test_file_reports.find do |report|
path == report.fetch("attributes").fetch("path")
end
end
 
def run
files = patches.each_with_object([]) do |patch, memo|
if patch.changed_lines.any? && (test_file_report = find_test_report(patch.file))
coverage = test_file_report.fetch("attributes").fetch("coverage")
 
Prefer `{...}` over `do...end` for functional blocks.
lines = patch.changed_line_numbers.map do |line_number|
Use `hits.positive?` instead of `hits > 0`.
covered = (hits = coverage[line_number - 1]) && hits > 0
Line.new(line_number, covered)
end
 
memo << File.new(patch.file, lines)
end
end
 
print_report(files)
end
 
def print_report(files)
max_path_length = files.map(&:path).map(&:length).max
format = "%#{max_path_length}s\t%d/%d %.2f%\n"
 
files.each do |file|
printf(format, file.path, file.lines.count(&:covered), file.lines.count, covered_lines_percentage(file.lines))
end
 
all_lines = files.map(&:lines).flatten
printf(format, "Total", all_lines.count(&:covered), all_lines.count, covered_lines_percentage(all_lines))
end
 
private
 
attr_reader :repo_slug, :cc_access_token, :github_access_token, :days_since
 
Method `test_report` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.
def test_report(index = 0)
@test_report ||= begin
sha = commits.fetch(index).sha
 
Prefer `{...}` over `do...end` for functional blocks.
response = cc_client.get("/v1/repos/#{cc_repo.fetch("id")}/test_reports") do |request|
request.params = { filter: { commit_sha: sha } }
end
 
JSON.parse(response.body).fetch("data").first or raise NoTestReportFound
end
rescue NoTestReportFound => ex
if index < 3
test_report(index + 1)
else
raise ex
end
end
 
def test_file_report(file_path)
endpoint = "/v1/repos/#{cc_repo.fetch("id")}/test_reports/#{test_report.fetch("id")}/test_file_reports"
 
Prefer `{...}` over `do...end` for functional blocks.
response = cc_client.get(endpoint) do |request|
request.params = { filter: { path: file_path } }
end
 
JSON.parse(response.body).fetch("data").first
end
 
def from_commit
@from_commit ||= begin
github_client.commits(repo_slug, until: Date.today - days_since).first
end
end
 
def commits
@commits ||= github_client.commits(repo_slug)
end
 
def cc_repo
@cc_repo ||= begin
Prefer `{...}` over `do...end` for functional blocks.
response = cc_client.get("/v1/repos") do |request|
request.params["github_slug"] = repo_slug
end
 
JSON.parse(response.body).fetch("data").first or raise "CC repo not found"
end
end
 
def eligible_file_paths
@eligible_file_paths ||=
patches.
Use 2 (not 0) spaces for indenting an expression in an assignment spanning multiple lines.
map(&:file).
Use 2 (not 0) spaces for indenting an expression in an assignment spanning multiple lines.
select { |filename| filename.end_with?("js", "rb", "py", "php") }.
Use 2 (not 0) spaces for indenting an expression in an assignment spanning multiple lines.
reject { |filename| filename.end_with?("_spec.rb") }
end
 
def test_file_reports
@test_file_reports ||= begin
reports = []
 
eligible_file_paths.each_slice(20) do |paths|
params = { page: { size: paths.count }, filter: { path: { "$in": paths } } }
reports << fetch_test_file_reports(params)
end
 
reports.flatten
end
end
 
def fetch_test_file_reports(params)
endpoint = "/v1/repos/#{cc_repo.fetch("id")}/test_reports/#{test_report.fetch("id")}/test_file_reports"
response = cc_client.get(endpoint) { |request| request.params = params }
 
JSON.parse(response.body).fetch("data")
end
 
def covered_lines_percentage(lines)
((lines.count(&:covered).to_f / lines.count) * 100).round(2)
end
 
def patches
@patches ||= GitDiffParser.parse(diff)
end
 
def diff
@diff ||= github_client.compare(
repo_slug,
from_commit.sha,
commits.first.sha,
accept: "application/vnd.github.diff",
)
end
 
def github_client
@github_client ||= Octokit::Client.new(access_token: github_access_token)
end
 
def cc_client
@cc_client ||= Faraday.new(
url: "https://api.codeclimate.com",
headers: {
"Accept": "application/vnd.api+json",
Avoid comma after the last item of a hash.
"Authorization": "Token token=#{cc_access_token}",
},
)
end
end
end