Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement branch coverage support for exit status modifiers #934

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Unreleased
==========

## Enhancements
* Can now define the minimum_coverage_by_file, maximum_coverage_drop and refuse_coverage_drop by branch as well as line coverage

0.20.0 (2020-11-29)
==========

Expand Down
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -787,30 +787,36 @@ to help ensure coverage is relatively consistent, rather than being skewed by pa

```ruby
SimpleCov.minimum_coverage_by_file 80
# same as above (the default is to check line coverage by file)
SimpleCov.minimum_coverage_by_file line: 80
# check for a minimum line coverage by file of 90% and minimum 80% branch coverage
SimpleCov.minimum_coverage_by_file line: 90, branch: 80
```

(not yet supported for branch coverage)

### Maximum coverage drop

You can define the maximum coverage drop percentage at once. SimpleCov will return non-zero if exceeded.

```ruby
SimpleCov.maximum_coverage_drop 5
# same as above (the default is to check line drop)
SimpleCov.maximum_coverage_drop line: 5
# check for a maximum line drop of 5% and maximum 10% branch drop
SimpleCov.maximum_coverage_drop line: 5, branch: 10
```

(not yet supported for branch coverage)

### Refuse dropping coverage

You can also entirely refuse dropping coverage between test runs:

```ruby
SimpleCov.refuse_coverage_drop
# same as above (the default is to only refuse line drop)
SimpleCov.refuse_coverage_drop :line
# refuse drop for line and branch
SimpleCov.refuse_coverage_drop :line, :branch
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating the docs and with nice examples to boot! 👍

```

(not yet supported for branch coverage)

## Using your own formatter

You can use your own formatter with:
Expand Down
137 changes: 132 additions & 5 deletions features/maximum_coverage_drop.feature
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Feature:
Background:
Given I'm working on the project "faked_project"

Scenario: maximum_coverage_drop configured cam cause spec failure
Scenario: maximum_coverage_drop configured can cause spec failure
Given SimpleCov for Test/Unit is configured with:
"""
require 'simplecov'
Expand All @@ -34,13 +34,13 @@ Feature:

When I run `bundle exec rake test`
Then the exit status should not be 0
And the output should contain "Coverage has dropped by 3.31% since the last time (maximum allowed: 3.14%)."
And the output should contain "Line coverage has dropped by 3.31% since the last time (maximum allowed: 3.14%)."
And a file named "coverage/.last_run.json" should exist
And the file "coverage/.last_run.json" should contain:
"""
{
"result": {
"covered_percent": 88.09
"line": 88.09
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is backwards incompatible, but I think it's fine. We should just mention it in the changelog. People might want to update their respective files to keep working after the release.

One thing though, we should write a test to make sure that we don't raise an error if an "old style" file is present. That'd be a catastrophic upgrade experience for folks if simplecov now failed all the time.

We could call it "covered_percent_branch" for branch coverage but it's not desirable long term, hence I'm fine with the incompatibility.

}
}
"""
Expand All @@ -61,7 +61,7 @@ Feature:
"""
{
"result": {
"covered_percent": 88.09
"line": 88.09
}
}
"""
Expand All @@ -84,7 +84,7 @@ Feature:
"""
{
"result": {
"covered_percent": 84.78
"line": 84.78
}
}
"""
Expand All @@ -110,6 +110,86 @@ Feature:
end
"""

And a file named "spec/failing_spec.rb" with:
"""
require "spec_helper"
describe FakedProject do
it "fails" do
expect(false).to eq(true)
end
end
"""
And the file named "coverage/.last_run.json" with:
"""
{
"result": {
"line": 100.0
}
}
"""

When I run `bundle exec rspec spec`
Then the exit status should be 1
And a file named "coverage/.last_run.json" should exist
And the file "coverage/.last_run.json" should contain:
"""
{
"result": {
"line": 100.0
}
}
"""
Scenario: When the previous last_run file has covered_percent
Given SimpleCov for Test/Unit is configured with:
"""
require 'simplecov'
SimpleCov.start do
add_filter 'test.rb'
end
"""
And the file "coverage/.last_run.json" with:
"""
{
"result": {
"covered_percent": 88.09
}
}
"""

When I run `bundle exec rake test`
Then the exit status should be 0
And a file named "coverage/.last_run.json" should exist
And the file "coverage/.last_run.json" should contain:
"""
{
"result": {
"line": 88.09
}
}
"""

Scenario: When the previous last_run file has covered_percent does not update resultset
Given SimpleCov for RSpec is configured with:
"""
require 'simplecov'
SimpleCov.start do
add_group 'Libs', 'lib/faked_project/'
add_filter '/spec/'
maximum_coverage_drop 0
end
"""

And a file named "lib/faked_project/missed.rb" with:
"""
class UncoveredSourceCode
def foo
never_reached
rescue => err
but no one cares about invalid ruby here
end
end
"""

And a file named "spec/failing_spec.rb" with:
"""
require "spec_helper"
Expand Down Expand Up @@ -139,3 +219,50 @@ Feature:
}
}
"""

@branch_coverage
Scenario: Works together with branch coverage and line coverage, announcing both failures
Given SimpleCov for Test/Unit is configured with:
"""
require 'simplecov'
SimpleCov.start do
add_filter 'test.rb'
enable_coverage :branch
maximum_coverage_drop line: 0, branch: 0
end
"""
And a file named "lib/faked_project/missed.rb" with:
"""
class UncoveredSourceCode
def foo
never_reached
rescue => err
but no one cares about invalid ruby here
end
end
"""

And a file named "spec/failing_spec.rb" with:
"""
require "spec_helper"
describe FakedProject do
it "fails" do
false ? true : expect(false).to eq(true)
end
end
"""
And the file named "coverage/.last_run.json" with:
"""
{
"result": {
"line": 100.0,
"branch": 100.0
}
}
"""

When I run `bundle exec rake test`
Then the exit status should not be 0
And the output should contain "Line coverage has dropped by 15.22% since the last time (maximum allowed: 0.00%)."
And the output should contain "Branch coverage has dropped by 50.00% since the last time (maximum allowed: 0.00%)."
And the output should contain "SimpleCov failed with exit 3"
10 changes: 5 additions & 5 deletions features/refuse_coverage_drop.feature
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Feature:
"""
{
"result": {
"covered_percent": 88.09
"line": 88.09
}
}
"""
Expand All @@ -42,13 +42,13 @@ Feature:

When I run `bundle exec rake test`
Then the exit status should not be 0
And the output should contain "Coverage has dropped by 3.31% since the last time (maximum allowed: 0.00%)."
And the output should contain "Line coverage has dropped by 3.31% since the last time (maximum allowed: 0.00%)."
And a file named "coverage/.last_run.json" should exist
And the file "coverage/.last_run.json" should contain:
"""
{
"result": {
"covered_percent": 88.09
"line": 88.09
}
}
"""
Expand All @@ -69,7 +69,7 @@ Feature:
"""
{
"result": {
"covered_percent": 88.09
"line": 88.09
}
}
"""
Expand All @@ -92,7 +92,7 @@ Feature:
"""
{
"result": {
"covered_percent": 84.78
"line": 84.78
}
}
"""
5 changes: 4 additions & 1 deletion lib/simplecov.rb
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,10 @@ def wait_for_other_processes
# @api private
#
def write_last_run(result)
SimpleCov::LastRun.write(result: {covered_percent: round_coverage(result.covered_percent)})
SimpleCov::LastRun.write(result:
result.coverage_statistics.transform_values do |stats|
round_coverage(stats.percent)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is very elegant 👌

end)
end

#
Expand Down
37 changes: 26 additions & 11 deletions lib/simplecov/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -287,20 +287,22 @@ def merge_timeout(seconds = nil)
#
# Default is 0% (disabled)
#

# rubocop:disable Metrics/CyclomaticComplexity
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

def minimum_coverage(coverage = nil)
return @minimum_coverage ||= {} unless coverage

coverage = {DEFAULT_COVERAGE_CRITERION => coverage} if coverage.is_a?(Numeric)

raise_on_invalid_coverage(coverage, "minimum_coverage")

@minimum_coverage = coverage
end

def raise_on_invalid_coverage(coverage, coverage_setting)
coverage.each_key { |criterion| raise_if_criterion_disabled(criterion) }
coverage.each_value do |percent|
minimum_possible_coverage_exceeded("minimum_coverage") if percent && percent > 100
minimum_possible_coverage_exceeded(coverage_setting) if percent && percent > 100
end

@minimum_coverage = coverage
end
# rubocop:enable Metrics/CyclomaticComplexity

#
# Defines the maximum coverage drop at once allowed for the testsuite to pass.
Expand All @@ -309,7 +311,13 @@ def minimum_coverage(coverage = nil)
# Default is 100% (disabled)
#
def maximum_coverage_drop(coverage_drop = nil)
@maximum_coverage_drop ||= (coverage_drop || 100).to_f.round(2)
return @maximum_coverage_drop ||= {} unless coverage_drop

coverage_drop = {DEFAULT_COVERAGE_CRITERION => coverage_drop} if coverage_drop.is_a?(Numeric)

raise_on_invalid_coverage(coverage_drop, "maximum_coverage_drop")

@maximum_coverage_drop = coverage_drop
end

#
Expand All @@ -320,16 +328,23 @@ def maximum_coverage_drop(coverage_drop = nil)
# Default is 0% (disabled)
#
def minimum_coverage_by_file(coverage = nil)
minimum_possible_coverage_exceeded("minimum_coverage_by_file") if coverage && coverage > 100
@minimum_coverage_by_file ||= (coverage || 0).to_f.round(2)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mental note that we're dropping the .to_f.round(2) which was probably there for a reason but I can't seem to find a reason to keep it right now so maybe we removed some unnecessary complexity 🚀

return @minimum_coverage_by_file ||= {} unless coverage

coverage = {DEFAULT_COVERAGE_CRITERION => coverage} if coverage.is_a?(Numeric)

raise_on_invalid_coverage(coverage, "minimum_coverage_by_file")

@minimum_coverage_by_file = coverage
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not for right now, but all these methods look very similar. I wonder if we could store them all just in one hash with their names as the keys and if that'd make these less repetitive.

Feel free to ignore that though, we can tackle this in a follow up (meaning you if you want to, or me when I get to it)

end

#
# Refuses any coverage drop. That is, coverage is only allowed to increase.
# SimpleCov will return non-zero if the coverage decreases.
#
def refuse_coverage_drop
maximum_coverage_drop 0
def refuse_coverage_drop(*criteria)
criteria = coverage_criteria if criteria.empty?

maximum_coverage_drop(criteria.map { |c| [c, 0] }.to_h)
end

#
Expand Down
Loading