Skip to content

grosser/single_cov

master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
lib
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Single Cov CI

Actionable code coverage.

rspec spec/foobar_spec.rb
......
114 example, 0 failures

lib/foobar.rb new uncovered lines introduced (2 current vs 0 configured)
Uncovered lines:
lib/foobar.rb:22
lib/foobar.rb:23:6-19
  • Missing coverage on every 💚 test run
  • Catch coverage issues before making PRs
  • Easily add coverage enforcement for legacy apps
  • 2-5% runtime overhead on small files, compared to 20% for SimpleCov
  • Branch coverage (disable via branches: false)
  • Use with forking_test_runner for exact per test coverage
# Gemfile
gem 'single_cov', group: :test

# spec/spec_helper.rb ... load single_cov before rails, libraries, minitest, or rspec
require 'single_cov'
SingleCov.setup :rspec # or :minitest

# spec/foobar_spec.rb ... add covered! call to test files
require 'spec_helper'
SingleCov.covered!

describe "xyz" do ...

Missing target file

Each covered! call expects to find a matching file, if it does not:

# change all guessed paths
SingleCov.rewrite { |f| f.sub('lib/unit/', 'app/models/') }

# mark directory as being in app and not lib
SingleCov::RAILS_APP_FOLDERS << 'presenters'

# add 1-off
SingleCov.covered! file: 'scripts/weird_thing.rb'

Known uncovered

Add the inline comment # uncovered to ignore uncovered code.

Prevent addition of new uncovered code, without having to cover all existing code by marking how many lines are uncovered:

SingleCov.covered! uncovered: 4

Making a folder not get prefixed with lib/

For example packwerk components are hosted in public and not lib/public

SingleCov::PREFIXES_TO_IGNORE << "public"

Missing coverage for implicit else in if or case statements

When a report shows for example 1:14-16 # else, that indicates that the implicit else is not covered.

# needs 2 tests: one for `true` and one for `false`
raise if a == b

# needs 2 tests: one for `when b` and one for `else`
case a
when b
end

Verify all code has tests & coverage

# spec/coverage_spec.rb
SingleCov.not_covered! # not testing any code in lib/

describe "Coverage" do
  # recommended
  it "does not allow new tests without coverage check" do
    # option :tests to pass custom Dir.glob results
    SingleCov.assert_used
  end

  # recommended
  it "does not allow new untested files" do
    # option :tests and :files to pass custom Dir.glob results
    # :untested to get it passing with known untested files
    SingleCov.assert_tested
  end
  
  # optional for full coverage enforcement
  it "does not reduce full coverage" do
    # make sure that nobody adds `uncovered: 123` to any test that did not have it before
    # option :tests to pass custom Dir.glob results
    # option :currently_complete for expected list of full covered tests
    # option :location for if you store that list in a separate file
    SingleCov.assert_full_coverage currently_complete: ["test/a_test.rb"]
  end
end

Automatic bootstrap

Run this from irb to get SingleCov added to all test files.

tests = Dir['spec/**/*_spec.rb']
command = "bundle exec rspec %{file}"

tests.each do |f|
  content = File.read(f)
  next if content.include?('SingleCov.')

  # add initial SingleCov call
  content = content.split(/\n/, -1)
  insert = content.index { |l| l !~ /require/ && l !~ /^#/ }
  content[insert...insert] = ["", "SingleCov.covered!"]
  File.write(f, content.join("\n"))

  # run the test to check coverage
  result = `#{command.sub('%{file}', f)} 2>&1`
  if $?.success?
    puts "#{f} is good!"
    next
  end

  if uncovered = result[/\((\d+) current/, 1]
    # configure uncovered
    puts "Uncovered for #{f} is #{uncovered}"
    content[insert+1] = "SingleCov.covered! uncovered: #{uncovered}"
    File.write(f, content.join("\n"))
  else
    # mark bad tests for manual cleanup
    content[insert+1] = "# SingleCov.covered! # TODO: manually fix this"
    File.write(f, content.join("\n"))
    puts "Manually fix: #{f} ... output is:\n#{result}"
  end
end

Cover multiple files from a single test

When a single integration test covers multiple source files.

SingleCov.covered! file: 'app/modes/user.rb'
SingleCov.covered! file: 'app/mailers/user_mailer.rb'
SingleCov.covered! file: 'app/controllers/user_controller.rb'

Generating a coverage report

SingleCov.coverage_report = "coverage/.resultset.json"
SingleCov.coverage_report_lines = true # only report line coverage for coverage systems that do not support branch coverage

Author

Michael Grosser
michael@grosser.it
License: MIT