class Danger::DangerShroud

Parse a Jacoco report to enforce code coverage on CI. Results are passed out as a table in markdown.

Shroud depends on having a Jacoco coverage report generated for your project. For Android projects, [jacoco-android-gradle-plugin](github.com/arturdm/jacoco-android-gradle-plugin) works well.

@example Running shroud with default values

# Report coverage of modified files, fail if either total project coverage
# or any modified file's coverage is under 90%
shroud.report 'path/to/jacoco/report.xml'

@example Running shroud with custom coverage thresholds

# Report coverage of modified files, fail if total project coverage is under 80%,
# or if any modified file's coverage is under 95%
shroud.report 'path/to/jacoco/report.xml', 80, 95

@example Warn on builds instead of fail

# Report coverage of modified files the same as the above example, except the
# builds will only warn instead of fail if below thresholds
shroud.report 'path/to/jacoco/report.xml', 80, 95, false

@tags android, jacoco, coverage

Public Instance Methods

report(file, totalProjectThreshold = 90, modifiedFileThreshold = 90, failIfUnderThreshold = true) click to toggle source

Report coverage on diffed files, as well as overall coverage.

@param [String] file

file path to a Jacoco xml coverage report.

@param [Integer] totalProjectThreshold

defines the required percentage of total project coverage for a passing build.
default 90.

@param [Integer] modifiedFileThreshold

defines the required percentage of files modified in a PR for a passing build.
default 90.

@param [Boolean] failIfUnderThreshold

if true, will fail builds that are under the provided thresholds. if false, will only warn.
default true.

@return [void]

# File lib/shroud/plugin.rb, line 50
def report(file, totalProjectThreshold = 90, modifiedFileThreshold = 90, failIfUnderThreshold = true)
  raise "Please specify file name." if file.empty?
  raise "No jacoco xml report found at #{file}" unless File.exist? file
  rawXml = File.read(file)
  parsedXml = Nokogiri::XML.parse(rawXml)
  totalInstructionCoverage = parsedXml.xpath("/report/counter[@type='INSTRUCTION']")
  missed = totalInstructionCoverage.attr("missed").value.to_i
  covered = totalInstructionCoverage.attr("covered").value.to_i
  total = missed + covered
  coveragePercent = (covered / total.to_f) * 100

  # get array of files names touched by this PR (modified + added)
  touchedFileNames = @dangerfile.git.modified_files.map { |file| File.basename(file) }
  touchedFileNames += @dangerfile.git.added_files.map { |file| File.basename(file) }

  # used to later report files that were modified but not included in the jacoco report
  fileNamesNotInJacocoReport = []

  # hash for keeping track of coverage per filename: {filename => coverage percent}
  touchedFilesHash = {}

  touchedFileNames.each do |touchedFileName|
    xmlForFileName = parsedXml.xpath("//class[@sourcefilename='#{touchedFileName}']/counter[@type='INSTRUCTION']")

    if (xmlForFileName.length > 0)
      missed = 0
      covered = 0
      xmlForFileName.each do |classCountXml|
        missed += classCountXml.attr("missed").to_i
        covered += classCountXml.attr("covered").to_i
      end
      touchedFilesHash[touchedFileName] = (covered.to_f / (missed + covered)) * 100
    else
      fileNamesNotInJacocoReport << touchedFileName
    end
  end

  puts "Here are unreported files"
  puts fileNamesNotInJacocoReport.to_s
  puts "Here is the touched files coverage hash"
  puts touchedFilesHash

  output = "## 🧛 Project Code Coverage: **`#{'%.2f' % coveragePercent}%`**\n"

  output << "### Coverage of Modified Files:\n"
  output << "File | Coverage\n"
  output << ":-----|:-----:\n"

  # go through each file:
  touchedFilesHash.sort.each do |fileName, coveragePercent|
    output << "`#{fileName}` | **`#{'%.2f' % coveragePercent}%`**\n"

    # warn or fail if under specified file threshold:
    if (coveragePercent < modifiedFileThreshold)
      warningMessage = "Uh oh! #{fileName} is under #{modifiedFileThreshold}% coverage!"
      if (failIfUnderThreshold)
        fail warningMessage
      else 
        warn warningMessage
      end
    end
  end

  output << "### Modified Files Not Found In Coverage Report:\n"
  fileNamesNotInJacocoReport.sort.each do |unreportedFileName| 
    output << "#{unreportedFileName}\n"
  end

  output << '> Codebase cunningly covered by count [Shroud 🧛](https://github.com/livefront/livefront-shroud-android/)'
  markdown output

  # warn or fail if total coverage is under specified threshold
  if (coveragePercent < totalProjectThreshold)
    totalCoverageWarning = "Uh oh! Your project is under #{totalProjectThreshold}% coverage!"
    if (failIfUnderThreshold) 
      fail totalCoverageWarning
    else 
      warn totalCoverageWarning
    end
  end
end