rspec-cover_it 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -10
- data/lib/rspec/cover_it/context.rb +27 -2
- data/lib/rspec/cover_it/context_coverage.rb +33 -6
- data/lib/rspec/cover_it/coverage_state.rb +7 -3
- data/lib/rspec/cover_it/example_group_completeness_checker.rb +34 -0
- data/lib/rspec/cover_it/version.rb +1 -1
- data/lib/rspec/cover_it.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5989ad87b5dccfd49f0e4074fde2aa5ee796af31c93d7a8c3b081fd5283db3aa
|
4
|
+
data.tar.gz: ef49e7f6d941accf93973aaf56da57a6ae60db7232042b9d06edad5d1845dc5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d64ad3f4a25abd3a5af67ab7e6ff3f548e7c43004c25c6c7dba5da4de81d5217a2160ae7c725c50af9fb9caecad57105c4d0f18cafb5b12a4968c63de849993
|
7
|
+
data.tar.gz: e0dee5c4480e921645112dcd5bfcbe303f144a90564d03542fe6b84b11159566807555b06c45382eb5a0302faba6470b786bcd38c4e5ba16563184a0bad52259
|
data/README.md
CHANGED
@@ -58,13 +58,20 @@ In example groups, you can use metadata to control the behavior of
|
|
58
58
|
this ExampleGroup. If numeric, it's enabling, and specifying the coverage
|
59
59
|
threshold at the same time (as a percentage - `cover_it: 95` requires 95%
|
60
60
|
coverage of the target class by this example group).
|
61
|
+
* `covers_path`: The path (relative to the spec file!) of the code the spec is
|
62
|
+
intending to cover. Later, this can be an array of paths, for the multi-spec
|
63
|
+
case `covers` is intended for as well. This is an annoying work-around for
|
64
|
+
the fact that we can't perfectly infer the location of the source code in
|
65
|
+
some cases - in particular, `lib/foo/version.rb` tends to cause a problem
|
66
|
+
for specs on `foo.rb`, since the version file is invariably loaded first.
|
61
67
|
* `covers`: An array of classes and modules that this example groups _thinks
|
62
68
|
it is completely testing_. Ideally, you'd have a separate test file for each,
|
63
69
|
but sometimes that's hard to do - you can let one spec claim responsibility
|
64
70
|
for multiple classes and modules (such as Concerns) using this. Be default
|
65
71
|
it is just `[described_class]`. Additionally, if your top-level example
|
66
72
|
group _does not describe a Class or Module_, you may use `covers` to let it
|
67
|
-
invoke `rspec-cover_it` anyway
|
73
|
+
invoke `rspec-cover_it` anyway - some people `describe "a descriptive string"`
|
74
|
+
instead of `describe AClass`, and .. fine.
|
68
75
|
|
69
76
|
## Implementation
|
70
77
|
|
@@ -115,18 +122,10 @@ That's not the goal here, and I'm not going to worry about it.
|
|
115
122
|
As initially implemented, it fails your tests if you don't run the entire test
|
116
123
|
file. `rspec spec/foo_spec.rb:32` will error, because .. running only one of
|
117
124
|
your tests _doesn't cover the class_. I have a solution for this, but it uses
|
118
|
-
some non-public bits of RSpec, so I'm trying to find a better answer
|
125
|
+
some non-public bits of RSpec, so I'm trying to find a better answer still.
|
119
126
|
(Conversation started in their
|
120
127
|
[issue tracker](https://github.com/rspec/rspec-core/issues/3037))
|
121
128
|
|
122
|
-
So far, I haven't found a _great_ way to report that there's a problem. The
|
123
|
-
output from having insufficient coverage is _raising an exception from an
|
124
|
-
`after(:context)` hook_, which RSpec rescues and formats for itself, and there
|
125
|
-
aren't a lot of controls - it works, but it's a bit ugly and doesn't really
|
126
|
-
give the right immediate impression. I'm contemplating using an `after(:suite)`
|
127
|
-
hook and aggregating them myself, but at the end of the day RSpec is in control
|
128
|
-
of the output stream, and we don't entirely fit its metaphor.
|
129
|
-
|
130
129
|
We're using `Object.const_source_location` to find the path of the source file
|
131
130
|
defining a given constant. That _mostly_ works, but it actually gives the path
|
132
131
|
of the _first_ source file that defined that constant. So if your gem defines
|
@@ -6,11 +6,18 @@ module RSpec
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def cover_it?
|
9
|
-
target_class &&
|
9
|
+
target_class &&
|
10
|
+
metadata.fetch(:cover_it, autoenforce?) &&
|
11
|
+
completeness_checker.running_all_examples?
|
12
|
+
end
|
13
|
+
|
14
|
+
def specific_threshold
|
15
|
+
meta_value = metadata.fetch(:cover_it, nil)
|
16
|
+
meta_value.is_a?(Numeric) ? meta_value / 100.0 : nil
|
10
17
|
end
|
11
18
|
|
12
19
|
def target_path
|
13
|
-
|
20
|
+
metadata.key?(:covers_path) ? metadata_path : inferred_path
|
14
21
|
end
|
15
22
|
|
16
23
|
def target_class
|
@@ -21,6 +28,10 @@ module RSpec
|
|
21
28
|
target_class.name
|
22
29
|
end
|
23
30
|
|
31
|
+
def scope_name
|
32
|
+
scope.file_path
|
33
|
+
end
|
34
|
+
|
24
35
|
private
|
25
36
|
|
26
37
|
attr_reader :scope, :rspec_context
|
@@ -32,6 +43,20 @@ module RSpec
|
|
32
43
|
def metadata
|
33
44
|
scope.metadata
|
34
45
|
end
|
46
|
+
|
47
|
+
def completeness_checker
|
48
|
+
@_completeness_checker ||= ExampleGroupCompletenessChecker.new(scope)
|
49
|
+
end
|
50
|
+
|
51
|
+
def metadata_path
|
52
|
+
supplied_path = metadata.fetch(:covers_path)
|
53
|
+
spec_directory = File.dirname(scope.file_path)
|
54
|
+
File.expand_path(supplied_path, spec_directory)
|
55
|
+
end
|
56
|
+
|
57
|
+
def inferred_path
|
58
|
+
Object.const_source_location(target_class_name).first
|
59
|
+
end
|
35
60
|
end
|
36
61
|
end
|
37
62
|
end
|
@@ -24,8 +24,29 @@ module RSpec
|
|
24
24
|
covered_line_count.to_f / coverable_line_count.to_f
|
25
25
|
end
|
26
26
|
|
27
|
-
def enforce!
|
28
|
-
|
27
|
+
def enforce!(default_threshold:)
|
28
|
+
if precovered?
|
29
|
+
fail_with_missing_code!
|
30
|
+
elsif local_coverage_rate < (context.specific_threshold || default_threshold)
|
31
|
+
fail_with_missing_coverage!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def fail_with_missing_code!
|
38
|
+
fail(MissingCode, <<~MESSAGE.tr("\n", " "))
|
39
|
+
Example group `#{context.scope_name}` is attempting to cover the code for class
|
40
|
+
`#{context.target_class}`, but it was located at `#{context.target_path}`,
|
41
|
+
and does not appear to have any code to cover (or it was all executed before the
|
42
|
+
tests started). If this is not the correct path for the code under test, please
|
43
|
+
specify the correct path using the `covers:` spec metadata - sometimes the
|
44
|
+
rspec-cover_it gem isn't properly able to infer the correct source path for a
|
45
|
+
class.
|
46
|
+
MESSAGE
|
47
|
+
end
|
48
|
+
|
49
|
+
def fail_with_missing_coverage!
|
29
50
|
lines = local_coverage.each_with_index.select { |v, _i| v&.zero? }.map(&:last)
|
30
51
|
|
31
52
|
summary =
|
@@ -36,11 +57,12 @@ module RSpec
|
|
36
57
|
else
|
37
58
|
"on #{lines.length} lines, including #{lines.first(10).map(&:to_s).join(", ")}"
|
38
59
|
end
|
39
|
-
message = "Missing coverage in #{context.target_path} #{summary}"
|
40
|
-
fail(MissingCoverage, message)
|
41
|
-
end
|
42
60
|
|
43
|
-
|
61
|
+
fail(MissingCoverage, <<~MESSAGE.tr("\n", " "))
|
62
|
+
Example group `#{context.scope_name}` is missing coverage on
|
63
|
+
`#{context.target_class}` in `#{context.target_path}` #{summary}
|
64
|
+
MESSAGE
|
65
|
+
end
|
44
66
|
|
45
67
|
attr_reader :context, :pretest_results
|
46
68
|
|
@@ -65,6 +87,11 @@ module RSpec
|
|
65
87
|
return nil unless local_coverage
|
66
88
|
local_coverage.count { |executions| executions && executions > 0 }
|
67
89
|
end
|
90
|
+
|
91
|
+
def precovered?
|
92
|
+
return @_precovered if defined?(@_precovered)
|
93
|
+
@_precovered = pretest_coverage.nil? || pretest_coverage.none? { |n| n&.zero? }
|
94
|
+
end
|
68
95
|
end
|
69
96
|
end
|
70
97
|
end
|
@@ -3,8 +3,8 @@ module RSpec
|
|
3
3
|
class CoverageState
|
4
4
|
attr_reader :filter
|
5
5
|
|
6
|
-
def initialize(filter: nil, autoenforce: false)
|
7
|
-
@filter, @autoenforce = filter, autoenforce
|
6
|
+
def initialize(filter: nil, autoenforce: false, default_threshold: 100.0)
|
7
|
+
@filter, @autoenforce, @default_threshold = filter, autoenforce, default_threshold
|
8
8
|
@pretest_results = nil
|
9
9
|
@context_coverages = {}
|
10
10
|
end
|
@@ -34,7 +34,7 @@ module RSpec
|
|
34
34
|
|
35
35
|
context_coverage_for(context).tap do |context_coverage|
|
36
36
|
context_coverage.postcontext_coverage = Coverage.peek_result[context.target_path]
|
37
|
-
context_coverage.enforce!
|
37
|
+
context_coverage.enforce!(default_threshold: default_threshold_rate)
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -46,6 +46,10 @@ module RSpec
|
|
46
46
|
@autoenforce
|
47
47
|
end
|
48
48
|
|
49
|
+
def default_threshold_rate
|
50
|
+
@default_threshold / 100.0
|
51
|
+
end
|
52
|
+
|
49
53
|
def context_for(scope, rspec_context)
|
50
54
|
Context.new(scope: scope, rspec_context: rspec_context, autoenforce: autoenforce?)
|
51
55
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module CoverIt
|
5
|
+
class ExampleGroupCompletenessChecker
|
6
|
+
# This class uses some bits of the RSpec::Core::ExampleGroup api that are
|
7
|
+
# not documented, and are marked `@private` using YARD notation. But I
|
8
|
+
# found no other reasonable way to answer this question, so I've isolated
|
9
|
+
# my intrusion into this class - hopefully, there will be a more
|
10
|
+
# appropriate way to determine this information in the future; I've begun
|
11
|
+
# that conversation here: https://github.com/rspec/rspec-core/issues/3037
|
12
|
+
|
13
|
+
def initialize(example_group)
|
14
|
+
@example_group = example_group
|
15
|
+
end
|
16
|
+
|
17
|
+
def running_all_examples?
|
18
|
+
all_examples == filtered_examples
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :example_group
|
24
|
+
|
25
|
+
def all_examples
|
26
|
+
@_all_examples ||= example_group.descendants.flat_map(&:examples).to_set
|
27
|
+
end
|
28
|
+
|
29
|
+
def filtered_examples
|
30
|
+
@_filtered_examples ||= example_group.descendant_filtered_examples.to_set
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/rspec/cover_it.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
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.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Mueller
|
@@ -99,6 +99,7 @@ files:
|
|
99
99
|
- lib/rspec/cover_it/context.rb
|
100
100
|
- lib/rspec/cover_it/context_coverage.rb
|
101
101
|
- lib/rspec/cover_it/coverage_state.rb
|
102
|
+
- lib/rspec/cover_it/example_group_completeness_checker.rb
|
102
103
|
- lib/rspec/cover_it/pretest_coverage.rb
|
103
104
|
- lib/rspec/cover_it/version.rb
|
104
105
|
- rspec-cover_it.gemspec
|