rspec-cover_it 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66c4b3e0c88dffe53a1d435d40d4e1bdb52038661d8abb89ccc29ef6974398e2
4
- data.tar.gz: 563d49e4e8656ea8da53defeb9e2d5f7ed9a91d36e5de0df126213f35dee6813
3
+ metadata.gz: b3efab53f0b9f05be2f5a8ae7f170e4a3a8e886439a751e025926c61bf9ac8f1
4
+ data.tar.gz: 569fbdcc5f328d5d0656eb3fe20265c9cd164455cfbf290c7764b92d15454fbf
5
5
  SHA512:
6
- metadata.gz: e17ac79db233e4a1f9571f98d025c37cf2e3c304155838fc221507ef5ac6a24aecb47be2f0a6678423009254a0bb5eac442ce775f4ad8bb3f9a66554e9f09fd6
7
- data.tar.gz: 0c9afcc6b5f019c185365e5c40925a3b96b4108133d1dcc6150edb182780c35e2a946693106fdf9299e507007a1adfb1dc33cbc341b95f729cea1faf202bbab2
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,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 first.
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 && metadata.fetch(:cover_it, autoenforce?)
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
- Object.const_source_location(target_class_name).first
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
- return if local_coverage.nil? || local_coverage_rate >= 1.0
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: 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
@@ -1,5 +1,5 @@
1
1
  module RSpec
2
2
  module CoverIt
3
- VERSION = "0.0.4"
3
+ VERSION = "0.0.5"
4
4
  end
5
5
  end
@@ -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
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
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