rspec-cover_it 0.0.7 → 0.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63e1b79c2f0b0d52869caa48e5d7dcfee100a726f1fb4698bcbef5d228179ec4
4
- data.tar.gz: 24d36270a6320b920f30134c8bb354b77325b16e4a311b1fb018470c72f0c5a7
3
+ metadata.gz: e0d27633b32f88f6dcc94af45b0ecd536b785c6229e3808ef0a1b95058f20b62
4
+ data.tar.gz: 0ade00b1ddfa4db191e9d927f6361bfcf8de243b543f2592ecbefd4a0b7f84de
5
5
  SHA512:
6
- metadata.gz: 3f8774a17ea6e1170471182cf768cbd7fc0e3f69a8cf2d04445d7a7ae0e7e04947b6a75205aa2d53eaaeef104597447608b3873413b73bfda2d9b83ce015bec6
7
- data.tar.gz: 3cf13d18a7658e6769d672e1d130ace9e3842ce1e3e7edfdf21e3c64753fb47668da6c7fa16690bc78e6741aa58cd03bdf1344d7c98b50a6fdbf51de03ba5ec1
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
@@ -0,0 +1,2 @@
1
+ all
2
+ rule "MD013", ignore_code_blocks: true
data/.mdlrc ADDED
@@ -0,0 +1,2 @@
1
+ style File.expand_path("../.mdl_rules.rb", __FILE__)
2
+ git_recurse true
@@ -0,0 +1,8 @@
1
+ ---
2
+ default_tools: ["standardrb", "rubocop", "markdown_lint", "rspec"]
3
+ executor: concurrent
4
+ comparison_branch: main
5
+ changed_files: false
6
+ filter_messages: false
7
+ logging: light
8
+ colorize: true
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
- Still a work in progress, but this gem is intended to give us a simpler and
4
- more _targeted_ way to manage coverage. The usual system for a large project
5
- is a single overarching SimpleCov target; with good tooling, there will be
6
- something checking that all of the lines of each PR get covered by the tests
7
- in that PR, and there are checks that the overall coverage number doesn't drop
8
- too far.. but it's a _sloppy, messy, lossy_ approach. Code loses coverage,
9
- code gets added with no tests, tests get removed and other code becomes less
10
- covered, and a lot of the time, that's because the _coverage is a lie_.
11
-
12
- Your coverage tool is telling you.. how many times each line of code _got run_
13
- during your entire test suite. But it's not telling you what is doing the
14
- running, which means that often large swathes of code are actually only covered
15
- incidentally, through tests that aren't intended to exercise that code, but
16
- _happens to_.
17
-
18
- This approach was moderately inspired by https://github.com/jamesdabbs/rspec-coverage,
19
- which was itself apparently inspired by a rubyconf 2016 talk by Ryan Davis. That
20
- talk and library are mostly concerned with making sure that we don't _overcount_
21
- coverage though, while this one has three goals:
22
-
23
- 1. Make coverage enforcement simpler
24
- 1. Enforce coverage _in the test suite_ (or nearly so)
25
- 1. Make coverage enforcement more local and specific.
26
-
27
- ## Tentative Usage
28
-
29
- Note that this bit isn't really implemented, but is more of an outline of how I
30
- intend the library to work. This is currently a spike, and not a functioning gem.
31
-
32
- You set up `RSpec::CoverIt` in your `spec_helper.rb` - require `rspec/cover_it`,
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
- rspec
98
-
99
- Randomized with seed 29392
100
- ...............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
101
- An error occurred in an `after(:context)` hook.
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
- ## Drawbacks and Shortcomings
115
-
116
- There's nothing in here that stops you from failing to write tests for a class
117
- at all! If you're using SimpleCov and you've got 100% coverage already, that's
118
- one of the benefits.. I could pretty reasonably include some kind of
119
- `after(:suite)` hook that optionally checks net coverage, but.. simplecov does
120
- that, and the concurrent-testing game makes this a _painful_ topic in reality.
121
- That's not the goal here, and I'm not going to worry about it.
122
-
123
- As initially implemented, it fails your tests if you don't run the entire test
124
- file. `rspec spec/foo_spec.rb:32` will error, because .. running only one of
125
- your tests _doesn't cover the class_. I have a solution for this, but it uses
126
- some non-public bits of RSpec, so I'm trying to find a better answer still.
127
- (Conversation started in their
128
- [issue tracker](https://github.com/rspec/rspec-core/issues/3037))
129
-
130
- We're using `Object.const_source_location` to find the path of the source file
131
- defining a given constant. That _mostly_ works, but it actually gives the path
132
- of the _first_ source file that defined that constant. So if your gem defines
133
- its version in `lib/foo/version.rb` (as an example), in a separate file from
134
- lib/foo.rb, the _path_ for `Foo` may end up being the former. Which is.. not
135
- going to have much coverable code, of course. This is an edge case, but one
136
- that is likely to occur fairly regularly. I haven't thought of a _good_ solution
137
- yet. Perhaps if the `covers` array includes a string, we should treat it as a
138
- relative path?
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
- Object.const_source_location(target_class_name).first
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
- return nil unless precontext_coverage && postcontext_coverage
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
- lines = local_coverage.each_with_index.select { |v, _i| v&.zero? }.map(&:last)
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}` #{summary}
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
- attr_reader :filter
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
- context_coverage_for(context).tap do |context_coverage|
27
- context_coverage.precontext_coverage = get_current_coverage(context.target_path)
28
- end
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
- context_coverage_for(context).tap do |context_coverage|
36
- context_coverage.postcontext_coverage = get_current_coverage(context.target_path)
37
- context_coverage.enforce!(default_threshold: default_threshold_rate)
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
- if result.any? { |_k, v| v.is_a?(Hash) }
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
@@ -1,5 +1,5 @@
1
1
  module RSpec
2
2
  module CoverIt
3
- VERSION = "0.0.7"
3
+ VERSION = "0.1.0"
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
+ 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
- RSpec::CoverIt.state.start_tracking
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
@@ -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.7
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-07 00:00:00.000000000 Z
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