lib/jacoco/plugin.rb in danger-jacoco-0.0.3 vs lib/jacoco/plugin.rb in danger-jacoco-0.1.0
- old
+ new
@@ -1,15 +1,23 @@
require 'jacoco/sax_parser'
module Danger
#
# @see Anton Malinskiy/danger-jacoco
- # @tags jacoco, coverage, java, android
+ # @tags jacoco, coverage, java, android, kotlin
#
class DangerJacoco < Plugin
- attr_accessor :minimum_coverage_percentage
+ attr_accessor :minimum_project_coverage_percentage
+ attr_accessor :minimum_class_coverage_percentage
+ attr_accessor :files_extension
+ def setup
+ @minimum_project_coverage_percentage = 0 unless minimum_project_coverage_percentage
+ @minimum_class_coverage_percentage = 0 unless minimum_class_coverage_percentage
+ @files_extension = ['.kt', '.java'] unless files_extension
+ end
+
# Parses the xml output of jacoco to Ruby model classes
# This is slow since it's basically DOM parsing
#
# @path path to the xml output of jacoco
#
@@ -19,59 +27,98 @@
# This is a fast report based on SAX parser
#
# @path path to the xml output of jacoco
# @delimiter git.modified_files returns full paths to the
- # changed files. We need to get the java class from this path to check the
+ # changed files. We need to get the class from this path to check the
# Jacoco report,
#
- # e.g. src/java/com/example/SomeClass.java -> com/example/SomeClass
+ # e.g. src/java/com/example/SomeJavaClass.java -> com/example/SomeJavaClass
+ # e.g. src/kotlin/com/example/SomeKotlinClass.kt -> com/example/SomeKotlinClass
#
# The default value supposes that you're using gradle structure,
- # that is your path to java source files is something like
+ # that is your path to source files is something like
#
- # blah/blah/java/slashed_package/Source.java
+ # Java => blah/blah/java/slashed_package/Source.java
+ # Kotlin => blah/blah/kotlin/slashed_package/Source.kt
#
- def report(path, delimiter = '/java/')
+ def report(path, delimiter = %r{\/java\/|\/kotlin\/})
+ setup
classes = classes(delimiter)
parser = Jacoco::SAXParser.new(classes)
Nokogiri::XML::SAX::Parser.new(parser).parse(File.open(path))
- parser.classes.each do |jacoco_class|
- # Check which metrics are available
- report_class(jacoco_class)
- end
+ total_covered = total_coverage(path)
+
+ report_markdown = "### JaCoCO Code Coverage #{total_covered[:covered]}% #{total_covered[:status]}\n"
+ report_markdown << "| Class | Covered | Meta | Status |\n"
+ report_markdown << "|:---:|:---:|:---:|:---:|\n"
+ markdown_class(parser, report_markdown)
+ markdown(report_markdown)
+
+ return if total_covered[:covered] >= minimum_project_coverage_percentage
+
+ # fail danger if total coveraged is smaller than minimum_project_coverage_percentage
+ covered = total_covered[:covered]
+ raise("Total coverage of #{covered}%. Improve this to as least #{minimum_project_coverage_percentage} %")
end
+ # Select modified and added files in this PR
def classes(delimiter)
git = @dangerfile.git
affected_files = git.modified_files + git.added_files
- affected_files.select { |file| file.end_with? '.java' }
- .map { |file| extract_class(file, delimiter) }
+ affected_files.select { |file| files_extension.reduce(false) { |state, el| state || file.end_with?(el) } }
+ .map { |file| file.split('.').first.split(delimiter)[1] }
end
+ # It returns a specific class code coverage and an emoji status as well
def report_class(jacoco_class)
counters = jacoco_class.counters
branch_counter = counters.detect { |e| e.type.eql? 'BRANCH' }
line_counter = counters.detect { |e| e.type.eql? 'LINE' }
counter = branch_counter.nil? ? line_counter : branch_counter
+ coverage = (counter.covered.fdiv(counter.covered + counter.missed) * 100).floor
+ status = coverage_status(coverage, minimum_class_coverage_percentage)
- report_counter(counter, jacoco_class)
+ {
+ covered: coverage,
+ status: status
+ }
end
- def report_counter(counter, jacoco_class)
- covered = counter.covered
- missed = counter.missed
- coverage = (covered.fdiv(covered + missed) * 100).floor
+ # it returns an emoji for coverage status
+ def coverage_status(coverage, minimum_percentage)
+ if coverage < (minimum_percentage / 2) then ':skull:'
+ elsif coverage < minimum_percentage then ':warning:'
+ else ':white_check_mark:'
+ end
+ end
- return unless coverage < minimum_coverage_percentage
+ # It returns total of project code coverage and an emoji status as well
+ def total_coverage(report_path)
+ jacoco_report = Nokogiri::XML(File.open(report_path))
- fail("#{jacoco_class.name} has coverage of #{coverage}%. " \
- "Improve this to at least #{minimum_coverage_percentage}%")
+ report = jacoco_report.xpath('report/counter').select { |item| item['type'] == 'INSTRUCTION' }
+ missed_instructions = report.first['missed'].to_f
+ covered_instructions = report.first['covered'].to_f
+ total_instructions = missed_instructions + covered_instructions
+ covered_percentage = (covered_instructions * 100 / total_instructions).round(2)
+ coverage_status = coverage_status(covered_percentage, minimum_project_coverage_percentage)
+
+ {
+ covered: covered_percentage,
+ status: coverage_status
+ }
end
- def extract_class(file, java_path_delimiter)
- file[0, file.length - 5].split(java_path_delimiter)[1]
+ private
+
+ def markdown_class(parser, report_markdown)
+ parser.classes.each do |jacoco_class| # Check metrics for each classes
+ rp = report_class(jacoco_class)
+ ln = "| `#{jacoco_class.name}` | #{rp[:covered]}% | #{minimum_class_coverage_percentage}% | #{rp[:status]} |\n"
+ report_markdown << ln
+ end
end
end
end