rspec-cover_it 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +18 -9
- data/lib/rspec/cover_it/context.rb +33 -4
- data/lib/rspec/cover_it/context_coverage.rb +28 -4
- data/lib/rspec/cover_it/coverage_state.rb +17 -5
- data/lib/rspec/cover_it/example_group_completeness_checker.rb +34 -0
- data/lib/rspec/cover_it/pretest_coverage.rb +1 -1
- data/lib/rspec/cover_it/version.rb +1 -1
- data/lib/rspec/cover_it.rb +3 -2
- 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: b3efab53f0b9f05be2f5a8ae7f170e4a3a8e886439a751e025926c61bf9ac8f1
|
4
|
+
data.tar.gz: 569fbdcc5f328d5d0656eb3fe20265c9cd164455cfbf290c7764b92d15454fbf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9d198749c87a093cf8cbf5d2e5a4f20bfdb1a4c312579a541d8335fa4ccab88781fdbee073195cf8d5c682d921019c9fb86ede9fe951e57f83df24722934c59
|
7
|
+
data.tar.gz: 604d4808f4a3d8afacf52cfe0651d4bbae46ee510ca95e123cd14304b071cbf1c726bf37741002efdd0d9dc636625b47d149f0a17226c26f42cdad0ba531be7f
|
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,14 +122,16 @@ 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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
+
We're using `Object.const_source_location` to find the path of the source file
|
130
|
+
defining a given constant. That _mostly_ works, but it actually gives the path
|
131
|
+
of the _first_ source file that defined that constant. So if your gem defines
|
132
|
+
its version in `lib/foo/version.rb` (as an example), in a separate file from
|
133
|
+
lib/foo.rb, the _path_ for `Foo` may end up being the former. Which is.. not
|
134
|
+
going to have much coverable code, of course. This is an edge case, but one
|
135
|
+
that is likely to occur fairly regularly. I haven't thought of a _good_ solution
|
136
|
+
yet. Perhaps if the `covers` array includes a string, we should treat it as a
|
137
|
+
relative path?
|
@@ -1,16 +1,23 @@
|
|
1
1
|
module RSpec
|
2
2
|
module CoverIt
|
3
3
|
class Context
|
4
|
-
def initialize(scope:, rspec_context:)
|
5
|
-
@scope, @rspec_context = scope, rspec_context
|
4
|
+
def initialize(scope:, rspec_context:, autoenforce:)
|
5
|
+
@scope, @rspec_context, @autoenforce = scope, rspec_context, autoenforce
|
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,13 +28,35 @@ 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
|
27
38
|
|
39
|
+
def autoenforce?
|
40
|
+
@autoenforce
|
41
|
+
end
|
42
|
+
|
28
43
|
def metadata
|
29
44
|
scope.metadata
|
30
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
|
31
60
|
end
|
32
61
|
end
|
33
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 =
|
@@ -40,8 +61,6 @@ module RSpec
|
|
40
61
|
fail(MissingCoverage, message)
|
41
62
|
end
|
42
63
|
|
43
|
-
private
|
44
|
-
|
45
64
|
attr_reader :context, :pretest_results
|
46
65
|
|
47
66
|
def target_path
|
@@ -65,6 +84,11 @@ module RSpec
|
|
65
84
|
return nil unless local_coverage
|
66
85
|
local_coverage.count { |executions| executions && executions > 0 }
|
67
86
|
end
|
87
|
+
|
88
|
+
def precovered?
|
89
|
+
return @_precovered if defined?(@_precovered)
|
90
|
+
@_precovered = pretest_coverage.nil? || pretest_coverage.none? { |n| n&.zero? }
|
91
|
+
end
|
68
92
|
end
|
69
93
|
end
|
70
94
|
end
|
@@ -3,8 +3,8 @@ module RSpec
|
|
3
3
|
class CoverageState
|
4
4
|
attr_reader :filter
|
5
5
|
|
6
|
-
def initialize(filter:)
|
7
|
-
@filter = filter
|
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
|
@@ -20,7 +20,7 @@ module RSpec
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def start_tracking_for(scope, rspec_context)
|
23
|
-
context =
|
23
|
+
context = context_for(scope, rspec_context)
|
24
24
|
return unless context.cover_it?
|
25
25
|
|
26
26
|
context_coverage_for(context).tap do |context_coverage|
|
@@ -29,12 +29,12 @@ module RSpec
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def finish_tracking_for(scope, rspec_context)
|
32
|
-
context =
|
32
|
+
context = context_for(scope, rspec_context)
|
33
33
|
return unless context.cover_it?
|
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
|
|
@@ -42,6 +42,18 @@ module RSpec
|
|
42
42
|
|
43
43
|
attr_reader :pretest_results
|
44
44
|
|
45
|
+
def autoenforce?
|
46
|
+
@autoenforce
|
47
|
+
end
|
48
|
+
|
49
|
+
def default_threshold_rate
|
50
|
+
@default_threshold / 100.0
|
51
|
+
end
|
52
|
+
|
53
|
+
def context_for(scope, rspec_context)
|
54
|
+
Context.new(scope: scope, rspec_context: rspec_context, autoenforce: autoenforce?)
|
55
|
+
end
|
56
|
+
|
45
57
|
def context_coverage_for(context)
|
46
58
|
@context_coverages[context.target_class] ||= ContextCoverage.new(
|
47
59
|
context: context,
|
@@ -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
@@ -3,6 +3,7 @@ require "coverage"
|
|
3
3
|
module RSpec
|
4
4
|
module CoverIt
|
5
5
|
Error = Class.new(StandardError)
|
6
|
+
MissingCode = Class.new(Error)
|
6
7
|
MissingCoverage = Class.new(Error)
|
7
8
|
end
|
8
9
|
end
|
@@ -16,8 +17,8 @@ module RSpec
|
|
16
17
|
attr_accessor :state
|
17
18
|
end
|
18
19
|
|
19
|
-
def self.setup(filter:)
|
20
|
-
RSpec::CoverIt.state = CoverageState.new(filter: filter)
|
20
|
+
def self.setup(filter: nil, autoenforce: false)
|
21
|
+
RSpec::CoverIt.state = CoverageState.new(filter: filter, autoenforce: autoenforce)
|
21
22
|
RSpec::CoverIt.state.start_tracking
|
22
23
|
|
23
24
|
RSpec.configure do |config|
|
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.5
|
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
|