rspec-cover_it 0.0.7 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/linters.yml +34 -0
- data/.github/workflows/rspec.yml +33 -0
- data/.mdl_rules.rb +2 -0
- data/.mdlrc +2 -0
- data/.quiet_quality.yml +8 -0
- data/.rspec +1 -0
- data/.rubocop.yml +21 -0
- data/README.md +110 -132
- data/lib/rspec/cover_it/context.rb +2 -5
- data/lib/rspec/cover_it/context_coverage.rb +46 -18
- data/lib/rspec/cover_it/coverage_state.rb +11 -22
- data/lib/rspec/cover_it/version.rb +1 -1
- data/lib/rspec/cover_it.rb +11 -1
- data/rspec-cover_it.gemspec +2 -0
- metadata +37 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0d27633b32f88f6dcc94af45b0ecd536b785c6229e3808ef0a1b95058f20b62
|
4
|
+
data.tar.gz: 0ade00b1ddfa4db191e9d927f6361bfcf8de243b543f2592ecbefd4a0b7f84de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e29cce1280f10140900b51f384b0e2e73a98e8b4fc1e79caf170e71e9c3cf64fac01fdcd5560472580c9544cde5169722db18508bfe119164e2f1dfe11dda9ba
|
7
|
+
data.tar.gz: 8895302c10ac12dbab2e71292e47f055067337d415650f8cd28671180d5690c1ea8ed59c4665cdc99ee9776af29e1372533b862cf73d2058fea3663c722c3525
|
@@ -0,0 +1,34 @@
|
|
1
|
+
name: Linters
|
2
|
+
|
3
|
+
on: [push]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
StandardRB:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v3
|
10
|
+
|
11
|
+
- name: Set up ruby
|
12
|
+
uses: ruby/setup-ruby@v1
|
13
|
+
with:
|
14
|
+
ruby-version: 3.1
|
15
|
+
|
16
|
+
- name: Cache gems
|
17
|
+
uses: actions/cache@v3
|
18
|
+
with:
|
19
|
+
path: vendor/bundle
|
20
|
+
key: ${{ runner.os }}-linters-${{ hashFiles('Gemfile.lock') }}
|
21
|
+
restore-keys:
|
22
|
+
${{ runner.os }}-linters-
|
23
|
+
|
24
|
+
- name: Install gems
|
25
|
+
run: bundle install --jobs 4 --retry 3
|
26
|
+
|
27
|
+
- name: Run standard
|
28
|
+
run: bundle exec standardrb
|
29
|
+
|
30
|
+
- name: Run rubocop (complexity checks)
|
31
|
+
run: bundle exec rubocop --parallel
|
32
|
+
|
33
|
+
- name: Run markdownlint
|
34
|
+
run: bundle exec mdl .
|
@@ -0,0 +1,33 @@
|
|
1
|
+
name: RSpec
|
2
|
+
|
3
|
+
on: [push]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
RSpec:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
strategy:
|
9
|
+
fail-fast: false
|
10
|
+
matrix:
|
11
|
+
ruby-version: ['2.7', '3.0', '3.1', '3.2', 'head']
|
12
|
+
|
13
|
+
steps:
|
14
|
+
- uses: actions/checkout@v3
|
15
|
+
|
16
|
+
- name: Set up ruby
|
17
|
+
uses: ruby/setup-ruby@v1
|
18
|
+
with:
|
19
|
+
ruby-version: ${{ matrix.ruby-version }}
|
20
|
+
|
21
|
+
- name: Cache gems
|
22
|
+
uses: actions/cache@v3
|
23
|
+
with:
|
24
|
+
path: vendor/bundle
|
25
|
+
key: ${{ runner.os }}-rspec-${{ matrix.ruby-version }}-${{ hashFiles('Gemfile.lock') }}
|
26
|
+
restore-keys:
|
27
|
+
${{ runner.os }}-rspec-${{ matrix.ruby-version }}-
|
28
|
+
|
29
|
+
- name: Install gems
|
30
|
+
run: bundle install --jobs 4 --retry 3
|
31
|
+
|
32
|
+
- name: Run RSpec
|
33
|
+
run: SIMPLECOV=true SIMPLECOV_TEXT=true bundle exec rspec
|
data/.mdl_rules.rb
ADDED
data/.mdlrc
ADDED
data/.quiet_quality.yml
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
AllCops:
|
3
|
+
SuggestExtensions: false
|
4
|
+
DisabledByDefault: true
|
5
|
+
|
6
|
+
Metrics/AbcSize:
|
7
|
+
Max: 15
|
8
|
+
Metrics/CyclomaticComplexity:
|
9
|
+
Max: 8
|
10
|
+
Metrics/PerceivedComplexity:
|
11
|
+
Max: 7
|
12
|
+
|
13
|
+
Metrics/ClassLength:
|
14
|
+
CountComments: false
|
15
|
+
Max: 150
|
16
|
+
Metrics/MethodLength:
|
17
|
+
CountComments: false
|
18
|
+
Max: 15
|
19
|
+
Metrics/ParameterLists:
|
20
|
+
Max: 5
|
21
|
+
CountKeywordArgs: true
|
data/README.md
CHANGED
@@ -1,138 +1,116 @@
|
|
1
1
|
# RSpec::CoverIt
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
coverage
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
and then `RSpec::CoverIt.setup(**config_options)` (before loading your code, as
|
34
|
-
you would with any other coverage library). It's.. _semi_ compatible with other
|
35
|
-
coverage systems - it only starts Coverage if it's not already running, and it
|
36
|
-
only uses `peek_result`, so it doesn't affect other systems outcomes.
|
37
|
-
Rough configuration options:
|
38
|
-
|
39
|
-
* `filter`: Only paths starting with this (matching this regex?) can matter
|
40
|
-
* `autoenforce`: off by default - with this disabled, you turn on coverage
|
41
|
-
enforcement for a given top-level describe ExampleGroup by adding metadata
|
42
|
-
like `cover_it: true` or `cover_it: 95`. If it's enabled though, you instead
|
43
|
-
can turn enforcement _off_ for an example group by setting `cover_it: false`.
|
44
|
-
* `global_threshold`: 100% by default. This whole "90% coverage is the right
|
45
|
-
target" thing is mostly a side-effect of the way we check the entire project
|
46
|
-
under one number.. but it's an easy setting to support, and I'm sure people
|
47
|
-
will disagree with me.
|
48
|
-
|
49
|
-
## Example Group Metadata
|
50
|
-
|
51
|
-
In example groups, you can use metadata to control the behavior of
|
52
|
-
`rspec-cover_it`. These keys have meaning:
|
53
|
-
|
54
|
-
* `cover_it`: if boolean, it enables or disables the coverage enforcement for
|
55
|
-
this ExampleGroup. If numeric, it's enabling, and specifying the coverage
|
56
|
-
threshold at the same time (as a percentage - `cover_it: 95` requires 95%
|
57
|
-
coverage of the target class by this example group).
|
58
|
-
* `covers_path`: The path (relative to the spec file!) of the code the spec is
|
59
|
-
intending to cover. Later, this can be an array of paths, for the multi-spec
|
60
|
-
case `covers` is intended for as well. This is an annoying work-around for
|
61
|
-
the fact that we can't perfectly infer the location of the source code in
|
62
|
-
some cases - in particular, `lib/foo/version.rb` tends to cause a problem
|
63
|
-
for specs on `foo.rb`, since the version file is invariably loaded first.
|
64
|
-
Note - in gems, this _frequently_ also happens when you glob-load a directory
|
65
|
-
_before_ defining the namespace they are all loading objects into. Then the
|
66
|
-
first file in that directory that loads ends up being the one that actually
|
67
|
-
creates the namespace.
|
68
|
-
* `covers`: An array of classes and modules that this example groups _thinks
|
69
|
-
it is completely testing_. Ideally, you'd have a separate test file for each,
|
70
|
-
but sometimes that's hard to do - you can let one spec claim responsibility
|
71
|
-
for multiple classes and modules (such as Concerns) using this. Be default
|
72
|
-
it is just `[described_class]`. Additionally, if your top-level example
|
73
|
-
group _does not describe a Class or Module_, you may use `covers` to let it
|
74
|
-
invoke `rspec-cover_it` anyway - some people `describe "a descriptive string"`
|
75
|
-
instead of `describe AClass`, and .. fine.
|
76
|
-
|
77
|
-
## Implementation
|
78
|
-
|
79
|
-
We record the coverage information in a `before(:suite)` rspec hook using
|
80
|
-
`Coverage.peek_result`, and hold onto that information. Then before and after
|
81
|
-
each 'context' (which really amounts to 'each spec file'), we grab the coverage
|
82
|
-
information again.
|
83
|
-
|
84
|
-
We use `Object.const_source_location` to find the file that defines the
|
85
|
-
`described_class`, and _that_ is what is being assessed for coverage. This
|
86
|
-
means that, if you are reopening the class somewhere else, that coverage won't
|
87
|
-
be checked; if you are including 15 Concerns, and don't intend to write separate
|
88
|
-
specs for them, be sure to list them as `covers:` metadata on the test. Also,
|
89
|
-
shame!
|
90
|
-
|
91
|
-
## Output
|
92
|
-
|
93
|
-
When there's missing coverage on the class under test, you'll currently see
|
94
|
-
output like this:
|
3
|
+
The standard approach to monitoring "test coverage" in ruby is a tool called
|
4
|
+
[SimpleCov](https://github.com/simplecov-ruby/simplecov), and a variety of
|
5
|
+
systems made to hook into its output, to display a single number that represents
|
6
|
+
how much of your _total code_ is executed during your test suite. You set up
|
7
|
+
SimpleCov, and then you commit your team to maintaining whatever test coverage
|
8
|
+
number you already have achieved globally, or to improving that coverage number.
|
9
|
+
If you have one of the better tools, you can commit to things like "all merged
|
10
|
+
PRs should have 100% (or 95%, etc) coverage on all of their touched code."
|
11
|
+
|
12
|
+
That's.. better than nothing. A lot better - it can especially tell you when you
|
13
|
+
forgot to write tests for some component, or when you're adjusting a class that
|
14
|
+
doesn't have any tests written for it. But it's _not great_, not compared to a
|
15
|
+
system like RSpec that lets you specify with _granularity_ how things should
|
16
|
+
behave. The problem with SimpleCov is that it's _holistic_ - you can't adopt
|
17
|
+
simplecov one class at a time, or enforce full coverage on new code _in your
|
18
|
+
CI system_, you need external tooling with state persistence for that.
|
19
|
+
|
20
|
+
`RSpec::CoverIt` takes a different approach, somewhat inspired by the
|
21
|
+
[rspec-coverage](https://github.com/jamesdabbs/rspec-coverage) gem (which was
|
22
|
+
itself apparently inspired by a rubyconf-2016 talk by Ryan Davis). With CoverIt,
|
23
|
+
coverage-enforcement happens as part of your test-suite, enforced by rspec
|
24
|
+
either globally or as specified.
|
25
|
+
|
26
|
+
## Installation and Setup
|
27
|
+
|
28
|
+
Add the gem to your Gemfile or gemspec just like `simplecov` or `rspec`. Then
|
29
|
+
in your `spec_helper.rb`, _before_ you require your application or gem code,
|
30
|
+
require `rspec/cover_it`, and then invoke `RSpec::CoverIt.setup`, with the
|
31
|
+
appropriate options to configure it (described further down). A reasonable
|
32
|
+
initial setup might look like this:
|
95
33
|
|
96
34
|
```
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
Failure/Error: fail(MissingCoverage, message)
|
103
|
-
Missing coverage in /Users/emueller/src/quiet_quality/lib/quiet_quality/message.rb on line 7
|
104
|
-
# /Users/emueller/src/rspec-cover_it/lib/rspec/cover_it/context_coverage.rb:40:in `enforce!'
|
105
|
-
# /Users/emueller/src/rspec-cover_it/lib/rspec/cover_it/coverage_state.rb:37:in `block in finish_tracking_for'
|
106
|
-
# /Users/emueller/src/rspec-cover_it/lib/rspec/cover_it/coverage_state.rb:35:in `finish_tracking_for'
|
107
|
-
# /Users/emueller/src/rspec-cover_it/lib/rspec/cover_it.rb:26:in `block (2 levels) in setup'
|
108
|
-
.....................................................................................................................................................................................................................................................................................................
|
109
|
-
|
110
|
-
Finished in 1.06 seconds (files took 0.28925 seconds to load)
|
111
|
-
852 examples, 0 failures, 1 error occurred outside of examples
|
35
|
+
require "rspec/cover_it"
|
36
|
+
project_root = File.expand_path("../..", __FILE__)
|
37
|
+
RSpec::CoverIt.setup(filter: project_root, autoenforce: true)
|
38
|
+
|
39
|
+
require File.expand_path("../../lib/my_gem", __FILE__)
|
112
40
|
```
|
113
41
|
|
114
|
-
##
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
42
|
+
## Configuration and Usage
|
43
|
+
|
44
|
+
When invoking `RSpec::CoverIt.setup`, you may supply these options:
|
45
|
+
|
46
|
+
* `filter`: Don't track coverage information about files that don't start with
|
47
|
+
this prefix - this is largely a performance optimization, allowing the pretest
|
48
|
+
coverage tracking to track information only about the current project, and not
|
49
|
+
the various gems it might depend on.
|
50
|
+
* `autoenforce`: This is off by default, which means that coverage-enforcement
|
51
|
+
will only be applied to top-level example groups that _request_ it, by
|
52
|
+
supplying the `cover_it: true` spec metaddata. If you configure `autoenforce`
|
53
|
+
to be `true`, then all specs will attempt to enforce coverage, as long as they
|
54
|
+
can figure out their targets (unless they are told not to).
|
55
|
+
|
56
|
+
When setting up a test file, you can configure some additional details for that
|
57
|
+
example group - `CoverIt` is aware of the following bits of spec metadata:
|
58
|
+
|
59
|
+
* `cover_it`: If supplied with a truthy value, it activates coverage enforcement
|
60
|
+
for this example-group - if supplied with a falsey value, it _deactivates_
|
61
|
+
enforcement. If supplied with a _numeric_ value, it is treated as a percentage,
|
62
|
+
and used to configure the target coverage threshold for the class' definition,
|
63
|
+
a feature which I intend not to use, but I expect some people to care deeply
|
64
|
+
about.
|
65
|
+
* `covers_path`: in certain cases, a class or module may be "defined" in several
|
66
|
+
locations. While actually enforcing coverage for multiple code files from one
|
67
|
+
test file isn't yet implemented, if `rspec-cover_it` infers the location of
|
68
|
+
the code under test _incorrectly_, you can tell it where to actually enforce
|
69
|
+
coverage against by supplying a path here (relative to the directory
|
70
|
+
containing the test file).
|
71
|
+
|
72
|
+
## Implementation Approach
|
73
|
+
|
74
|
+
When setting up the tool, we activate the built-in ruby Coverage system (we
|
75
|
+
start it in legacy-supporting mode, but we are compatible with it having already
|
76
|
+
been started in the more modern mode as well). Then we register three rspec
|
77
|
+
hooks:
|
78
|
+
|
79
|
+
* Before the suite _starts_ (which should be after everything is loaded), we
|
80
|
+
record the coverage _so far_ - this is the 'initialization' coverage, which
|
81
|
+
mostly includes the lines that run during class evaluation.
|
82
|
+
* Before each 'context' (spec file), we record the coverage information
|
83
|
+
* After each 'context' we record the coverage information _again_. Then we
|
84
|
+
subtract the two sets of coverage, which tells us how many times each line
|
85
|
+
was run _during this example group_, and add it to the 'initialization'
|
86
|
+
coverage to see the effective coverage for just this test file.
|
87
|
+
|
88
|
+
Then, for each test file, we use ruby's source-introspection system to tell
|
89
|
+
us where the "described class" was defined, and check what fraction of that
|
90
|
+
source file is effectively covered by this example group. If there is some
|
91
|
+
missing coverage, we raise an exception explaining the missing coverage.
|
92
|
+
|
93
|
+
But of course, if you only run part of the tests in a spec file (perhaps by
|
94
|
+
specifying a line number, or a description filter with `-e`), it probably won't
|
95
|
+
be fully covered. We don't want to fail the test suite every time you're working
|
96
|
+
on the specs for something, so we reach into RSpec a little to check if the
|
97
|
+
set of _filtered_ examples (the ones being run) match the _full_ set of
|
98
|
+
examples. If they don't, don't bother checking coverage for this test file.
|
99
|
+
|
100
|
+
## Caveats and Shortcomings
|
101
|
+
|
102
|
+
For the moment, there is no support for _autoloading_, which makes this library
|
103
|
+
awkward to use for the majority of large rails applications. I intend to resolve
|
104
|
+
this issue soon, but it's not a trivial one, as coverage-tracking on a per-spec
|
105
|
+
basis relies on being able to separate coverage that happens during code-loading
|
106
|
+
from coverage that happens during "tests other than this spec which have already
|
107
|
+
occurred" - we'll need to add some kind of autoloading hooks to support it
|
108
|
+
properly.
|
109
|
+
|
110
|
+
Until then, the gem will only be useful to a rails application in an eager-
|
111
|
+
loaded context - that's not a major issue in CI, but it's awkward when running
|
112
|
+
tests locally. Happily, `rspec-cover_it` is fully compatible with `simplecov`
|
113
|
+
(though you do need to start `SimpleCov` _before_ `Rspec::CoverIt`), so you can
|
114
|
+
simply use simplecov locally when resolving coverage issues (or set up eager-
|
115
|
+
loading locally based on an environment variable, the solution I prefer), and
|
116
|
+
still use `RSpec::CoverIt` to enforce coverage in your CI system.
|
@@ -24,10 +24,6 @@ module RSpec
|
|
24
24
|
metadata.fetch(:described_class, nil)
|
25
25
|
end
|
26
26
|
|
27
|
-
def target_class_name
|
28
|
-
target_class.name
|
29
|
-
end
|
30
|
-
|
31
27
|
def scope_name
|
32
28
|
scope.file_path
|
33
29
|
end
|
@@ -55,7 +51,8 @@ module RSpec
|
|
55
51
|
end
|
56
52
|
|
57
53
|
def inferred_path
|
58
|
-
|
54
|
+
return nil if target_class.nil?
|
55
|
+
Object.const_source_location(target_class.name).first
|
59
56
|
end
|
60
57
|
end
|
61
58
|
end
|
@@ -8,23 +8,22 @@ module RSpec
|
|
8
8
|
|
9
9
|
attr_accessor :precontext_coverage, :postcontext_coverage
|
10
10
|
|
11
|
-
def pretest_coverage
|
12
|
-
@_pretest_coverage ||= pretest_results[target_path]
|
13
|
-
end
|
14
|
-
|
15
11
|
def local_coverage
|
16
|
-
|
12
|
+
assert_ready!
|
13
|
+
return nil unless precontext_coverage && postcontext_coverage && coverable_lines?
|
17
14
|
@_local_coverage ||= pretest_coverage
|
18
15
|
.zip(precontext_coverage, postcontext_coverage)
|
19
16
|
.map { |a, b, c| line_calculation(a, b, c) }
|
20
17
|
end
|
21
18
|
|
22
19
|
def local_coverage_rate
|
20
|
+
assert_ready!
|
23
21
|
return nil unless covered_line_count
|
24
22
|
covered_line_count.to_f / coverable_line_count.to_f
|
25
23
|
end
|
26
24
|
|
27
25
|
def enforce!(default_threshold:)
|
26
|
+
assert_ready!
|
28
27
|
if precovered?
|
29
28
|
fail_with_missing_code!
|
30
29
|
elsif local_coverage_rate < (context.specific_threshold || default_threshold)
|
@@ -34,6 +33,19 @@ module RSpec
|
|
34
33
|
|
35
34
|
private
|
36
35
|
|
36
|
+
def pretest_coverage
|
37
|
+
@_pretest_coverage ||= pretest_results[target_path]
|
38
|
+
end
|
39
|
+
|
40
|
+
def coverable_lines?
|
41
|
+
pretest_coverage.compact.any?
|
42
|
+
end
|
43
|
+
|
44
|
+
def assert_ready!
|
45
|
+
return if precontext_coverage && postcontext_coverage
|
46
|
+
fail(NotReady, "ContextCoverage was not ready yet, something has gone wrong")
|
47
|
+
end
|
48
|
+
|
37
49
|
def fail_with_missing_code!
|
38
50
|
fail(MissingCode, <<~MESSAGE.tr("\n", " "))
|
39
51
|
Example group `#{context.scope_name}` is attempting to cover the code for class
|
@@ -46,21 +58,37 @@ module RSpec
|
|
46
58
|
MESSAGE
|
47
59
|
end
|
48
60
|
|
61
|
+
def uncovered_lines
|
62
|
+
@_uncovered_lines ||= local_coverage.each_with_index.select { |v, _i| v&.zero? }.map(&:last)
|
63
|
+
end
|
64
|
+
|
65
|
+
def single_uncovered_line_summary
|
66
|
+
"on line #{uncovered_lines.first}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def few_uncovered_lines_summary
|
70
|
+
"on lines #{uncovered_lines.map(&:to_s).join(", ")}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def many_uncovered_lines_summary
|
74
|
+
shortened_list = uncovered_lines.first(10).map(&:to_s).join(", ")
|
75
|
+
"on #{uncovered_lines.length} lines, including #{shortened_list}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def uncovered_lines_summary
|
79
|
+
if uncovered_lines.length == 1
|
80
|
+
single_uncovered_line_summary
|
81
|
+
elsif uncovered_lines.length <= 10
|
82
|
+
few_uncovered_lines_summary
|
83
|
+
else
|
84
|
+
many_uncovered_lines_summary
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
49
88
|
def fail_with_missing_coverage!
|
50
|
-
|
51
|
-
|
52
|
-
summary =
|
53
|
-
if lines.length == 1
|
54
|
-
"on line #{lines.first}"
|
55
|
-
elsif lines.length <= 10
|
56
|
-
"on lines #{lines.map(&:to_s).join(", ")}"
|
57
|
-
else
|
58
|
-
"on #{lines.length} lines, including #{lines.first(10).map(&:to_s).join(", ")}"
|
59
|
-
end
|
60
|
-
|
61
|
-
fail(MissingCoverage, <<~MESSAGE.tr("\n", " "))
|
89
|
+
fail(MissingCoverage, <<~MESSAGE.tr("\n", " ").strip)
|
62
90
|
Example group `#{context.scope_name}` is missing coverage on
|
63
|
-
`#{context.target_class}` in `#{context.target_path}` #{
|
91
|
+
`#{context.target_class}` in `#{context.target_path}` #{uncovered_lines_summary}
|
64
92
|
MESSAGE
|
65
93
|
end
|
66
94
|
|
@@ -1,7 +1,8 @@
|
|
1
1
|
module RSpec
|
2
2
|
module CoverIt
|
3
3
|
class CoverageState
|
4
|
-
|
4
|
+
attr_accessor :pretest_results
|
5
|
+
attr_reader :filter, :context_coverages
|
5
6
|
|
6
7
|
def initialize(filter: nil, autoenforce: false, default_threshold: 100.0)
|
7
8
|
@filter, @autoenforce, @default_threshold = filter, autoenforce, default_threshold
|
@@ -23,25 +24,22 @@ module RSpec
|
|
23
24
|
context = context_for(scope, rspec_context)
|
24
25
|
return unless context.cover_it?
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
context_coverage = ContextCoverage.new(context: context, pretest_results: pretest_results)
|
28
|
+
context_coverage.precontext_coverage = get_current_coverage(context.target_path)
|
29
|
+
@context_coverages[context.target_class] = context_coverage
|
29
30
|
end
|
30
31
|
|
31
32
|
def finish_tracking_for(scope, rspec_context)
|
32
33
|
context = context_for(scope, rspec_context)
|
33
34
|
return unless context.cover_it?
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
36
|
+
context_coverage = @context_coverages.fetch(context.target_class)
|
37
|
+
context_coverage.postcontext_coverage = get_current_coverage(context.target_path)
|
38
|
+
context_coverage.enforce!(default_threshold: default_threshold_rate)
|
39
39
|
end
|
40
40
|
|
41
41
|
private
|
42
42
|
|
43
|
-
attr_reader :pretest_results
|
44
|
-
|
45
43
|
def autoenforce?
|
46
44
|
@autoenforce
|
47
45
|
end
|
@@ -54,25 +52,16 @@ module RSpec
|
|
54
52
|
Context.new(scope: scope, rspec_context: rspec_context, autoenforce: autoenforce?)
|
55
53
|
end
|
56
54
|
|
57
|
-
def context_coverage_for(context)
|
58
|
-
@context_coverages[context.target_class] ||= ContextCoverage.new(
|
59
|
-
context: context,
|
60
|
-
pretest_results: pretest_results
|
61
|
-
)
|
62
|
-
end
|
63
|
-
|
64
55
|
def get_current_coverage(path = nil)
|
65
56
|
result = Coverage.peek_result
|
66
57
|
|
67
58
|
if path
|
68
59
|
value = result[path]
|
69
60
|
value.is_a?(Hash) ? value.fetch(:lines) : value
|
61
|
+
elsif result.any? { |_k, v| v.is_a?(Hash) }
|
62
|
+
result.transform_values { |v| v.fetch(:lines) }
|
70
63
|
else
|
71
|
-
|
72
|
-
result.transform_values { |v| v.fetch(:lines) }
|
73
|
-
else
|
74
|
-
result
|
75
|
-
end
|
64
|
+
result
|
76
65
|
end
|
77
66
|
end
|
78
67
|
end
|
data/lib/rspec/cover_it.rb
CHANGED
@@ -3,6 +3,7 @@ require "coverage"
|
|
3
3
|
module RSpec
|
4
4
|
module CoverIt
|
5
5
|
Error = Class.new(StandardError)
|
6
|
+
NotReady = Class.new(Error)
|
6
7
|
MissingCode = Class.new(Error)
|
7
8
|
MissingCoverage = Class.new(Error)
|
8
9
|
end
|
@@ -19,10 +20,19 @@ module RSpec
|
|
19
20
|
|
20
21
|
def self.setup(filter: nil, autoenforce: false)
|
21
22
|
RSpec::CoverIt.state = CoverageState.new(filter: filter, autoenforce: autoenforce)
|
22
|
-
|
23
|
+
setup_pretest_tracking
|
24
|
+
setup_per_context_tracking
|
25
|
+
end
|
23
26
|
|
27
|
+
private_class_method def self.setup_pretest_tracking
|
28
|
+
RSpec::CoverIt.state.start_tracking
|
24
29
|
RSpec.configure do |config|
|
25
30
|
config.prepend_before(:suite) { RSpec::CoverIt.state.finish_load_tracking }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private_class_method def self.setup_per_context_tracking
|
35
|
+
RSpec.configure do |config|
|
26
36
|
config.prepend_before(:context) { |context| RSpec::CoverIt.state.start_tracking_for(self.class, context) }
|
27
37
|
config.append_after(:context) { |context| RSpec::CoverIt.state.finish_tracking_for(self.class, context) }
|
28
38
|
end
|
data/rspec-cover_it.gemspec
CHANGED
@@ -31,6 +31,8 @@ Gem::Specification.new do |spec|
|
|
31
31
|
|
32
32
|
spec.add_development_dependency "pry", "~> 0.14"
|
33
33
|
spec.add_development_dependency "standard", "~> 1.28"
|
34
|
+
spec.add_development_dependency "rubocop", "~> 1.28"
|
34
35
|
spec.add_development_dependency "mdl", "~> 0.12"
|
35
36
|
spec.add_development_dependency "quiet_quality", "~> 1.2"
|
37
|
+
spec.add_development_dependency "simplecov", "~> 0.22.0"
|
36
38
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-cover_it
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Mueller
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-06-
|
11
|
+
date: 2023-06-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.28'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.28'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.28'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: mdl
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +94,20 @@ dependencies:
|
|
80
94
|
- - "~>"
|
81
95
|
- !ruby/object:Gem::Version
|
82
96
|
version: '1.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.22.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.22.0
|
83
111
|
description: |
|
84
112
|
We're all used to tools that enforce _total_ coverage numbers, but this gem
|
85
113
|
tries for something different. Instead of keeping your whole project above
|
@@ -91,7 +119,14 @@ executables: []
|
|
91
119
|
extensions: []
|
92
120
|
extra_rdoc_files: []
|
93
121
|
files:
|
122
|
+
- ".github/workflows/linters.yml"
|
123
|
+
- ".github/workflows/rspec.yml"
|
94
124
|
- ".gitignore"
|
125
|
+
- ".mdl_rules.rb"
|
126
|
+
- ".mdlrc"
|
127
|
+
- ".quiet_quality.yml"
|
128
|
+
- ".rspec"
|
129
|
+
- ".rubocop.yml"
|
95
130
|
- Gemfile
|
96
131
|
- LICENSE
|
97
132
|
- README.md
|