From 88b238b1bb3224e402918d3c12574080e5bcc091 Mon Sep 17 00:00:00 2001 From: Michael Grosser Date: Wed, 14 Feb 2018 17:49:29 -0800 Subject: [PATCH] branch coverage --- .travis.yml | 2 ++ Readme.md | 5 +++-- lib/single_cov.rb | 34 +++++++++++++++++++++++++++++++--- specs/single_cov_spec.rb | 35 ++++++++++++++++++++++++++++++----- 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 17c5888..e4a3d11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ bundler_args: "" +branches: + only: master rvm: - 2.0 - 2.1 diff --git a/Readme.md b/Readme.md index 8c61383..b1c4077 100644 --- a/Readme.md +++ b/Readme.md @@ -4,6 +4,7 @@ Actionable code coverage. - Get actionable feedback on every successful test run - Only 2-5% runtime overhead on small files compared to 50% for `SimpleCov` - No more PRs with bad test coverage + - Branch coverage on ruby 2.5+ ```Ruby # Gemfile @@ -11,7 +12,7 @@ gem 'single_cov', group: :test # spec/spec_helper.rb ... load before loading rails / minitest / libraries require 'single_cov' -SingleCov.setup :rspec +SingleCov.setup :rspec, branches: true # spec/foobar_spec.rb ... add covered! call to every test file require 'spec_helper' @@ -36,7 +37,7 @@ lib/foobar.rb:23 Call setup before loading minitest. ```Ruby -SingleCov.setup :minitest +SingleCov.setup :minitest, branches: true require 'minitest/autorun' ``` diff --git a/lib/single_cov.rb b/lib/single_cov.rb index 0f60d3b..02a54e7 100644 --- a/lib/single_cov.rb +++ b/lib/single_cov.rb @@ -18,9 +18,17 @@ def covered!(file: nil, uncovered: 0) end def all_covered?(result) + result = result[:lines] || result + errors = COVERAGES.map do |file, expected_uncovered| if coverage = result["#{root}/#{file}"] - uncovered_lines = coverage.each_with_index.map { |c, i| "#{file}:#{i+1}" if c == 0 }.compact + lines = (coverage.is_a?(Hash) ? coverage.fetch(:lines) : coverage) + uncovered_lines = line_coverage(file, lines) + + if branches = (coverage.is_a?(Hash) && coverage[:branches]) + uncovered_lines += branch_coverage(file, branches) + end + next if uncovered_lines.size == expected_uncovered warn_about_bad_coverage(file, expected_uncovered, uncovered_lines) else @@ -58,10 +66,16 @@ def assert_tested(files: glob('{app,lib}/**/*.rb'), tests: default_tests, untest end end - def setup(framework, root: nil) + def setup(framework, root: nil, branches: false) if defined?(SimpleCov) raise "Load SimpleCov after SingleCov" end + if branches && RUBY_VERSION < "2.5.0" + warn "Branch coverage needs ruby 2.5.0" + @branches = false + else + @branches = branches + end @root = root if root @@ -89,6 +103,16 @@ def disable private + def line_coverage(file, coverage) + coverage.each_with_index.map { |c, i| "#{file}:#{i + 1}" if c == 0 }.compact + end + + def branch_coverage(file, coverage) + coverage.each_value.flat_map do |branch_part| + branch_part.select { |_k, v| v == 0 }.map { |k, _| "#{file}:#{k[2]} branch #{k[2]}:#{k[3]+1}-#{k[4]}:#{k[5]+1}" } + end + end + def default_tests glob("{test,spec}/**/*_{test,spec}.rb") end @@ -110,7 +134,11 @@ def coverage_results # SimpleCov might start coverage again, but that does not hurt ... def start_coverage_recording require 'coverage' - Coverage.start + if @branches + Coverage.start(lines: true, branches: true) + else + Coverage.start + end end # not running rake or a whole folder diff --git a/specs/single_cov_spec.rb b/specs/single_cov_spec.rb index de5f669..a230892 100644 --- a/specs/single_cov_spec.rb +++ b/specs/single_cov_spec.rb @@ -3,6 +3,15 @@ SingleCov.instance_variable_set(:@root, File.expand_path("../fixtures/minitest", __FILE__)) describe SingleCov do + def self.it_does_not_complain_when_everything_is_covered + it "does not complain when everything is covered" do + result = sh "ruby test/a_test.rb" + assert_tests_finished_normally(result) + expect(result).to_not include "uncovered" + end + end + + it "has a VERSION" do expect(SingleCov::VERSION).to match(/^[\.\da-z]+$/) end @@ -12,11 +21,7 @@ around { |test| Dir.chdir("specs/fixtures/minitest", &test) } - it "does not complain when everything is covered" do - result = sh "ruby test/a_test.rb" - assert_tests_finished_normally(result) - expect(result).to_not include "uncovered" - end + it_does_not_complain_when_everything_is_covered it "is silent" do result = sh "ruby test/a_test.rb" @@ -177,6 +182,26 @@ end end end + + describe "branch coverage" do + around { |t| change_file("test/a_test.rb", "root: root", "root: root, branches: true", &t) } + + it_does_not_complain_when_everything_is_covered + + describe "with branches" do + around { |t| change_file("lib/a.rb", "1", "2.times { |i| rand if i == 0 }", &t) } + + it_does_not_complain_when_everything_is_covered + + it "complains when coverage is missing" do + change_file("lib/a.rb", "i == 0", "i != i") do + result = sh "ruby test/a_test.rb", fail: true + expect(result).to include ".lib/a.rb new uncovered lines introduced (1 current vs 0 configured)" + expect(result).to include "lib/a.rb:3 branch 3:19-3:23" + end + end + end + end if RUBY_VERSION >= "2.5.0" end describe "rspec" do