Class: Danger::DangerJacoco
- Inherits:
-
Plugin
- Object
- Plugin
- Danger::DangerJacoco
- Defined in:
- lib/jacoco/plugin.rb
Overview
Verify code coverage inside your projects This is done using the jacoco output Results are passed out as a table in markdown
Instance Attribute Summary collapse
-
#class_column_title ⇒ Object
rubocop:disable Metrics/ClassLength.
-
#fail_no_coverage_data_found ⇒ Object
rubocop:disable Metrics/ClassLength.
-
#file_to_create_on_failure ⇒ Object
rubocop:disable Metrics/ClassLength.
-
#files_extension ⇒ Object
rubocop:disable Metrics/ClassLength.
-
#files_to_check ⇒ Object
rubocop:disable Metrics/ClassLength.
-
#minimum_class_coverage_map ⇒ Object
rubocop:disable Metrics/ClassLength.
-
#minimum_class_coverage_percentage ⇒ Object
rubocop:disable Metrics/ClassLength.
-
#minimum_composable_class_coverage_percentage ⇒ Object
rubocop:disable Metrics/ClassLength.
-
#minimum_package_coverage_map ⇒ Object
rubocop:disable Metrics/ClassLength.
-
#minimum_project_coverage_percentage ⇒ Object
rubocop:disable Metrics/ClassLength.
-
#subtitle_failure ⇒ Object
rubocop:disable Metrics/ClassLength.
-
#subtitle_success ⇒ Object
rubocop:disable Metrics/ClassLength.
-
#title ⇒ Object
rubocop:disable Metrics/ClassLength.
Instance Method Summary collapse
-
#add_kotlin_declarations(file, package_path, class_to_file_path_hash) ⇒ Object
Scans a Kotlin file for additional class/interface declarations and adds them to the hash.
-
#classes(delimiter) ⇒ Object
rubocop:enable Style/AbcSize.
-
#coverage_status(coverage, minimum_percentage) ⇒ Object
it returns an emoji for coverage status.
-
#filtered_files_to_check ⇒ Object
Returns files that match the configured file extensions.
-
#package_coverage(class_name) ⇒ Object
it returns the most suitable coverage by package name to class or nil.
-
#parse(path) ⇒ Object
Parses the xml output of jacoco to Ruby model classes This is slow since it’s basically DOM parsing.
-
#report(path, report_url = '', delimiter = %r{/java/|/kotlin/}, fail_no_coverage_data_found: true) ⇒ Object
This is a fast report based on SAX parser.
-
#report_class(jacoco_class, file_path) ⇒ Object
It returns a specific class code coverage and an emoji status as well.
-
#required_class_coverage(jacoco_class, file_path) ⇒ Object
Determines the required coverage for the class rubocop:disable Metrics/AbcSize rubocop:disable Metrics/CyclomaticComplexity.
-
#setup ⇒ Object
Initialize the plugin with configured parameters or defaults.
-
#setup_minimum_coverages ⇒ Object
Initialize the plugin with configured coverage minimum parameters or defaults.
-
#setup_texts ⇒ Object
Initialize the plugin with configured optional texts.
-
#total_coverage(report_path) ⇒ Object
It returns total of project code coverage and an emoji status as well.
Instance Attribute Details
#class_column_title ⇒ Object
rubocop:disable Metrics/ClassLength
23 24 25 |
# File 'lib/jacoco/plugin.rb', line 23 def class_column_title @class_column_title end |
#fail_no_coverage_data_found ⇒ Object
rubocop:disable Metrics/ClassLength
23 24 25 |
# File 'lib/jacoco/plugin.rb', line 23 def fail_no_coverage_data_found @fail_no_coverage_data_found end |
#file_to_create_on_failure ⇒ Object
rubocop:disable Metrics/ClassLength
23 24 25 |
# File 'lib/jacoco/plugin.rb', line 23 def file_to_create_on_failure @file_to_create_on_failure end |
#files_extension ⇒ Object
rubocop:disable Metrics/ClassLength
23 24 25 |
# File 'lib/jacoco/plugin.rb', line 23 def files_extension @files_extension end |
#files_to_check ⇒ Object
rubocop:disable Metrics/ClassLength
23 24 25 |
# File 'lib/jacoco/plugin.rb', line 23 def files_to_check @files_to_check end |
#minimum_class_coverage_map ⇒ Object
rubocop:disable Metrics/ClassLength
23 24 25 |
# File 'lib/jacoco/plugin.rb', line 23 def minimum_class_coverage_map @minimum_class_coverage_map end |
#minimum_class_coverage_percentage ⇒ Object
rubocop:disable Metrics/ClassLength
23 24 25 |
# File 'lib/jacoco/plugin.rb', line 23 def minimum_class_coverage_percentage @minimum_class_coverage_percentage end |
#minimum_composable_class_coverage_percentage ⇒ Object
rubocop:disable Metrics/ClassLength
23 24 25 |
# File 'lib/jacoco/plugin.rb', line 23 def minimum_composable_class_coverage_percentage @minimum_composable_class_coverage_percentage end |
#minimum_package_coverage_map ⇒ Object
rubocop:disable Metrics/ClassLength
23 24 25 |
# File 'lib/jacoco/plugin.rb', line 23 def minimum_package_coverage_map @minimum_package_coverage_map end |
#minimum_project_coverage_percentage ⇒ Object
rubocop:disable Metrics/ClassLength
23 24 25 |
# File 'lib/jacoco/plugin.rb', line 23 def minimum_project_coverage_percentage @minimum_project_coverage_percentage end |
#subtitle_failure ⇒ Object
rubocop:disable Metrics/ClassLength
23 24 25 |
# File 'lib/jacoco/plugin.rb', line 23 def subtitle_failure @subtitle_failure end |
#subtitle_success ⇒ Object
rubocop:disable Metrics/ClassLength
23 24 25 |
# File 'lib/jacoco/plugin.rb', line 23 def subtitle_success @subtitle_success end |
#title ⇒ Object
rubocop:disable Metrics/ClassLength
23 24 25 |
# File 'lib/jacoco/plugin.rb', line 23 def title @title end |
Instance Method Details
#add_kotlin_declarations(file, package_path, class_to_file_path_hash) ⇒ Object
Scans a Kotlin file for additional class/interface declarations and adds them to the hash
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/jacoco/plugin.rb', line 131 def add_kotlin_declarations(file, package_path, class_to_file_path_hash) return unless File.exist?(file) file_content = File.read(file) # Look for class and interface declarations in the file # Regex catches class/interface/object declarations with modifiers and generics regex = /\b(?:(?:data|sealed|abstract|open|internal|private|protected|public|inline)\s+)* (?:class|interface|object)\s+([A-Za-z0-9_]+)(?:<.*?>)?/x declarations = file_content.scan(regex).flatten # For each additional class/interface found (excluding the one matching the filename) declarations.each do |class_name| # Skip if it matches the primary class name (already added) next if package_path.end_with?("/#{class_name}") # Create full class path by replacing the last part with the class name parts = package_path.split('/') parts[-1] = class_name additional_class_path = parts.join('/') # Add to hash class_to_file_path_hash[additional_class_path] = file end end |
#classes(delimiter) ⇒ Object
rubocop:enable Style/AbcSize
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/jacoco/plugin.rb', line 106 def classes(delimiter) class_to_file_path_hash = {} # Initialize files_extension if it's nil @files_extension = ['.kt', '.java'] if @files_extension.nil? filtered_files_to_check.each do |file| # "src/java/com/example/CachedRepository.java" # Get the package path package_path = file.split('.').first.split(delimiter)[1] # "com/example/CachedRepository" next unless package_path # Add the primary class (filename-based class) class_to_file_path_hash[package_path] = file # For Kotlin files, we need to look for multiple classes/interfaces in the same file add_kotlin_declarations(file, package_path, class_to_file_path_hash) if file.end_with?('.kt') end class_to_file_path_hash end |
#coverage_status(coverage, minimum_percentage) ⇒ Object
it returns an emoji for coverage status
211 212 213 214 215 216 217 |
# File 'lib/jacoco/plugin.rb', line 211 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 |
#filtered_files_to_check ⇒ Object
Returns files that match the configured file extensions
126 127 128 |
# File 'lib/jacoco/plugin.rb', line 126 def filtered_files_to_check files_to_check.select { |file| @files_extension.reduce(false) { |state, el| state || file.end_with?(el) } } end |
#package_coverage(class_name) ⇒ Object
it returns the most suitable coverage by package name to class or nil
197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/jacoco/plugin.rb', line 197 def package_coverage(class_name) path = class_name package_parts = class_name.split('/') package_parts.reverse_each do |item| size = item.size path = path[0...-size] coverage = minimum_package_coverage_map[path] path = path[0...-1] unless path.empty? return coverage unless coverage.nil? end nil end |
#parse(path) ⇒ Object
Parses the xml output of jacoco to Ruby model classes This is slow since it’s basically DOM parsing
59 60 61 |
# File 'lib/jacoco/plugin.rb', line 59 def parse(path) Jacoco::DOMParser.read_path(path) end |
#report(path, report_url = '', delimiter = %r{/java/|/kotlin/}, fail_no_coverage_data_found: true) ⇒ Object
This is a fast report based on SAX parser
changed files. We need to get the class from this path to check the Jacoco report,
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 source files is something like
Java => blah/blah/java/slashed_package/Source.java Kotlin => blah/blah/kotlin/slashed_package/Source.kt
rubocop:disable Style/AbcSize
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/jacoco/plugin.rb', line 81 def report(path, report_url = '', delimiter = %r{/java/|/kotlin/}, fail_no_coverage_data_found: true) @fail_no_coverage_data_found = fail_no_coverage_data_found setup class_to_file_path_hash = classes(delimiter) classnames = class_to_file_path_hash.keys parser = Jacoco::SAXParser.new(classnames) Nokogiri::XML::SAX::Parser.new(parser).parse(File.open(path)) total_covered = total_coverage(path) header = "### #{title} Code Coverage #{total_covered[:covered]}% #{total_covered[:status]}\n" report_markdown = header report_markdown += "| #{class_column_title} | Covered | Required | Status |\n" report_markdown += "|:---|:---:|:---:|:---:|\n" class_coverage_above_minimum = markdown_class(parser, report_markdown, report_url, class_to_file_path_hash) subtitle = class_coverage_above_minimum ? subtitle_success : subtitle_failure report_markdown.insert(header.length, "#### #{subtitle}\n") markdown(report_markdown) report_fails(parser, report_url, class_coverage_above_minimum, total_covered) end |
#report_class(jacoco_class, file_path) ⇒ Object
It returns a specific class code coverage and an emoji status as well
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/jacoco/plugin.rb', line 158 def report_class(jacoco_class, file_path) report_result = { covered: 'No coverage data found : -', status: ':black_joker:', required_coverage_percentage: 'No coverage data found : -' } counter = coverage_counter(jacoco_class) unless counter.nil? coverage = (counter.covered.fdiv(counter.covered + counter.missed) * 100).floor required_coverage = required_class_coverage(jacoco_class, file_path) status = coverage_status(coverage, required_coverage) report_result = { covered: coverage, status: status, required_coverage_percentage: required_coverage } end report_result end |
#required_class_coverage(jacoco_class, file_path) ⇒ Object
Determines the required coverage for the class rubocop:disable Metrics/AbcSize rubocop:disable Metrics/CyclomaticComplexity
184 185 186 187 188 189 190 191 192 |
# File 'lib/jacoco/plugin.rb', line 184 def required_class_coverage(jacoco_class, file_path) key = minimum_class_coverage_map.keys.detect { |k| jacoco_class.name.match(k) } || jacoco_class.name required_coverage = minimum_class_coverage_map[key] includes_composables = File.read(file_path).include? '@Composable' if File.exist?(file_path) required_coverage = minimum_composable_class_coverage_percentage if required_coverage.nil? && includes_composables required_coverage = package_coverage(jacoco_class.name) if required_coverage.nil? required_coverage = minimum_class_coverage_percentage if required_coverage.nil? required_coverage end |
#setup ⇒ Object
Initialize the plugin with configured parameters or defaults
29 30 31 32 33 34 35 |
# File 'lib/jacoco/plugin.rb', line 29 def setup setup_minimum_coverages setup_texts @files_to_check = [] unless files_to_check @files_extension = ['.kt', '.java'] unless files_extension @file_to_create_on_failure = 'danger_jacoco_failure_status_file.json' unless file_to_create_on_failure end |
#setup_minimum_coverages ⇒ Object
Initialize the plugin with configured coverage minimum parameters or defaults
46 47 48 49 50 51 52 |
# File 'lib/jacoco/plugin.rb', line 46 def setup_minimum_coverages @minimum_project_coverage_percentage = 0 unless minimum_project_coverage_percentage @minimum_class_coverage_percentage = 0 unless minimum_class_coverage_percentage @minimum_composable_class_coverage_percentage = 0 unless minimum_composable_class_coverage_percentage @minimum_package_coverage_map = {} unless minimum_package_coverage_map @minimum_class_coverage_map = {} unless minimum_class_coverage_map end |
#setup_texts ⇒ Object
Initialize the plugin with configured optional texts
38 39 40 41 42 43 |
# File 'lib/jacoco/plugin.rb', line 38 def setup_texts @title = 'JaCoCo' unless title @class_column_title = 'Class' unless class_column_title @subtitle_success = 'All classes meet coverage requirement. Well done! :white_check_mark:' unless subtitle_success @subtitle_failure = 'There are classes that do not meet coverage requirement :warning:' unless subtitle_failure end |
#total_coverage(report_path) ⇒ Object
It returns total of project code coverage and an emoji status as well
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/jacoco/plugin.rb', line 220 def total_coverage(report_path) jacoco_report = Nokogiri::XML(File.open(report_path)) 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 |