Smackdown
Smackdown lets you compare changes introduced by a particular git repo's branch, and compares that against a simplecov-format test coverage report in order to report on changed or added lines that are not covered by any tests.
The main intended use case for this is as a test coverage "linter" for pull requests, to make sure that new or changed code is supported by tests.
Basic Usage
Say we're working on a feature branch called my_feature, that we originally branched off of master.  In order
to check that all our new and changed code is covered by tests, do the following:
- 
If you don't already have simplecov set up, check out the README to get started: https://github.com/colszowka/simplecov.
 - 
Install the
simplecov-jsongem and configure simplecov to output JSON as well: 
Gemfile
gem 'simplecov-json', require: false
gem 'smackdown'Rakefile
require 'smackdown'test_helper.rb (or wherever you are running SimpleCov.start)
require 'simplecov'
require 'simplecov-json'
SimpleCov.start do
  SimpleCov.formatters = [
    SimpleCov::Formatter::HTMLFormatter,
    SimpleCov::Formatter::JSONFormatter
  ]
end- Run your tests to produce the coverage report (
coverage/coverage.json). 
bundle exec rake test
- Run smackdown.
 
bundle exec rake smackdown
=> All new and modified code is covered!
- If your branch doesn't have tests for all new and modified code you've committed (compared to master), you'll instead see a report resembling this:
 
lib/class_with_uncovered_lines.rb
18:     puts "This method was added but no test is written to cover it!"
19:     @some_var = 5
-----------------------------------------
lib/new_class_with_complete_coverage.rb
100% covered!
-----------------------------------------
lib/new_class_with_no_coverage.rb
No coverage for this file!
-----------------------------------------
- After you add new tests, remember to re-run your test suite to generate a new coverage report for smackdown to use.
 
Advanced Usage (Example)
lib/tasks/my_custom_smackdown_task.rake
desc 'My custom smackdown task'
task :smackdown_custom do
  reporter = Smackdown::CoverageDiffReporter.new(
    "path/to/repo",
    coverage_report_path: "/path/to/repo/coverage/coverage.json",  # This can be a filepath or even a url
    report_path_prefix: "/path/to/repo",  # A string to prefix to files' relative paths in order to make them match the paths
                                          # recorded in the coverage report
    head: "my_feature_for_next_release",
    merge_base: "release_candidate_2016_08_01",
    filters: Smackdown::CoverageDiffReporter.DEFAULT_FILTERS + %w(one_time_scripts/)
  )
  reporter.run
  if reporter.completely_covered?
    puts "All new and modified code is covered!"
  else
    reporter.report do |file_coverage_diff|
    puts file_coverage_diff.relative_path
    if file_coverage_diff.coverage_available?
      if file_coverage_diff.covered?
        puts "100\% covered!"
      else
        file_coverage_diff.uncovered_lines.each do |line|
          puts "#{line.line_num}: #{line.line_content}"
        end
      end
    else
      puts "No coverage for this file!"
    end
    puts "\n-----------------------------------------\n"
  end
  exit(reporter.completely_covered?)
endNOTES:
- This is example usage and what you do in the block passed to the CoverageDiffReporter#report method is up to you.
 - Many of the options passed to CoverageDiffReporter#new are optional and default to common values.  See 
lib/coverage_diff_reporter.rbfor more information. - Instead of passing the path to the coverage report in the 
coverage_report_pathparameter, you can instead pass a json string in ascoverage_json: my_json_string 
Setting up and Running Tests
Clone the repo, then:
cd smackdown
bundle install
git submodule init
git submodule update --remote
ruby test/script/init.rb  # This will run the tests in the smackdown_test_repo subproject on the my_branch 
                          # and full_smackdown_coverage branches and produce test coverage reports for each
bundle exec rake test
Open coverage/index.html and verify that you have 100% test coverage!
If you create a branch (i.e. in order to contribute to the gem), you can even use smackdown on the smackdown repo itself in order to verify that your branch's changes are all tested:
bundle exec rake smackdown  # dogfooding!
How the Tests Work
The test setup here is a little unusual due to the fact that this gem's functionality fundamentally involves comparing different revisions in a git repo.
We test this by making use of an ACTUAL git repo which exists solely for the purpose of testing the smackdown gem, and which is included here as a submodule (hence the submodule init and update steps): https://github.com/robesris/smackdown_test_repo
The smackdown_test_repo contains three branches as of this writing: master, my_branch, and full_smackdown_coverage.  The master branch includes some sample "app code" that simply defines a few classes with arbitrary methods, as well as a tiny test suite.  The test suite tests one of the methods in the class_with_uncovered_code_added.rb file (covered_method) but not the other (existing_uncovered_method).
Now, my_branch represents code changes from master, and adds a new method (really just uncomments the uncovered_method method).  However, it does not add any tests to cover this new method.  It also adds a new file with comprehensive tests (new_class_with_complete_coverage.rb), and omits tests entirely for another new file (new_class_with_no_coverage.rb)
Finally, the full_smackdown_coverage branch adds tests for all new and changed code that remains untested in my_branch, for the purpose of testing a 100% coverage scenario.
Running ruby test/script/init.rb as described above runs the test suite on the full_smackdown_coverage and my_branch branches, and creates a coverage report for each in the submodule root as coverage/full_smackdown_coverage.json and coverage/coverage.json, respectively.
What we want to see, in order to verify that smackdown is functioning correctly, is as follows:
- The new untested method (
uncovered_method) is identified bysmackdownas newly-introduced but uncovered code. - The untested method that already existed on 
master(existing_uncovered_method) is ignored, as it was not introduced by the code changes made inmy_branch. - The file with no coverage at all (
new_class_with_no_coverage) is reported as such. - The 
full_smackdown_coveragebranch is reported as having no changed or added code that is untested. 
The tests in the main smackdown repo are geared towards testing for these characteristics.
Credit
Thanks to @ivantsepp (and his lintrunner gem: https://github.com/ivantsepp/lintrunner) for help and inspiration
getting started with this project!