rspec-covers 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.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +202 -0
  4. data/data/evaluation/ground_truth.example.json +26 -0
  5. data/lib/minitest/covers.rb +133 -0
  6. data/lib/rspec/covers/checked_coverage.rb +27 -0
  7. data/lib/rspec/covers/code_location.rb +49 -0
  8. data/lib/rspec/covers/configuration.rb +135 -0
  9. data/lib/rspec/covers/declaration.rb +199 -0
  10. data/lib/rspec/covers/declaration_validation.rb +65 -0
  11. data/lib/rspec/covers/evaluation.rb +197 -0
  12. data/lib/rspec/covers/formatter.rb +70 -0
  13. data/lib/rspec/covers/integration.rb +54 -0
  14. data/lib/rspec/covers/metadata_reader.rb +34 -0
  15. data/lib/rspec/covers/method_entry.rb +19 -0
  16. data/lib/rspec/covers/method_label.rb +37 -0
  17. data/lib/rspec/covers/probe/call_log_probe.rb +132 -0
  18. data/lib/rspec/covers/probe/coverage_probe.rb +57 -0
  19. data/lib/rspec/covers/production_inventory.rb +90 -0
  20. data/lib/rspec/covers/rake_task.rb +63 -0
  21. data/lib/rspec/covers/reporter.rb +162 -0
  22. data/lib/rspec/covers/source_range.rb +37 -0
  23. data/lib/rspec/covers/static_method_scanner.rb +184 -0
  24. data/lib/rspec/covers/strict_verdict.rb +107 -0
  25. data/lib/rspec/covers/tracer/composite.rb +73 -0
  26. data/lib/rspec/covers/tracer/dynamic.rb +27 -0
  27. data/lib/rspec/covers/tracer/dynamic_corpus.rb +43 -0
  28. data/lib/rspec/covers/tracer/lcba.rb +17 -0
  29. data/lib/rspec/covers/tracer/nc.rb +36 -0
  30. data/lib/rspec/covers/tracer/ncc.rb +23 -0
  31. data/lib/rspec/covers/tracer/suggestion.rb +17 -0
  32. data/lib/rspec/covers/tracer/tokenizer.rb +18 -0
  33. data/lib/rspec/covers/version.rb +11 -0
  34. data/lib/rspec/covers.rb +225 -0
  35. data/sig/minitest/covers.rbs +6 -0
  36. data/sig/rspec/covers.rbs +27 -0
  37. metadata +134 -0
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ require_relative "covers/version"
6
+
7
+ module RSpec
8
+ module Covers
9
+ class Error < StandardError; end
10
+
11
+ class << self
12
+ attr_writer :configuration, :reporter
13
+
14
+ def configuration
15
+ @configuration ||= Configuration.new
16
+ end
17
+
18
+ def configure(rspec_config = nil)
19
+ yield configuration if block_given?
20
+
21
+ Integration.install!(rspec_config) if rspec_config
22
+ configuration
23
+ end
24
+
25
+ def install!(rspec_config)
26
+ Integration.install!(rspec_config)
27
+ end
28
+
29
+ def installed?
30
+ @installed == true
31
+ end
32
+
33
+ def mark_installed!
34
+ @installed = true
35
+ end
36
+
37
+ def reporter
38
+ @reporter ||= Reporter.new
39
+ end
40
+
41
+ def coverage_probe
42
+ @coverage_probe ||= Probe::CoverageProbe.new(configuration)
43
+ end
44
+
45
+ def call_log_probe
46
+ @call_log_probe ||= Probe::CallLogProbe.new(configuration)
47
+ end
48
+
49
+ def dynamic_corpus
50
+ @dynamic_corpus ||= Tracer::DynamicCorpus.new
51
+ end
52
+
53
+ def run_example(example)
54
+ Thread.current[:rspec_covers_expectation_object_ids] = []
55
+ before_snapshot = coverage_probe.snapshot
56
+ call_log_probe.start_example if call_log_enabled?
57
+
58
+ example.run
59
+ ensure
60
+ call_log = call_log_enabled? ? call_log_probe.stop_example : Probe::CallLog.new(calls: [], returns_by_object_id: {})
61
+ executed = coverage_probe.difference(before_snapshot || {}, coverage_probe.snapshot)
62
+ process_example(example, executed, call_log) if example
63
+ Thread.current[:rspec_covers_expectation_object_ids] = nil
64
+ end
65
+
66
+ def record_expectation_actual(actual)
67
+ ids = Thread.current[:rspec_covers_expectation_object_ids]
68
+ ids << actual.__id__ if ids
69
+ call_log_probe.record_expectation(actual) if call_log_enabled?
70
+ end
71
+
72
+ def process_example(example, executed_locations, call_log)
73
+ declaration = Declaration.from_example(example, configuration)
74
+ dynamic_corpus.record(example.id, call_log.labels) if configuration.suggest || configuration.validate_declarations
75
+ measured_locations = measured_locations(executed_locations, declaration, call_log)
76
+ verdict = StrictVerdict.call(
77
+ executed_locations: measured_locations,
78
+ declaration: declaration,
79
+ config: configuration
80
+ )
81
+ example.metadata[:rspec_covers_risky] = verdict.risky?
82
+ suggestions = suggestions_for(example, declaration, call_log, executed_locations)
83
+ validation = validation_for(example, declaration, call_log, executed_locations)
84
+ unchecked_locations = unchecked_locations_for(executed_locations, measured_locations)
85
+
86
+ reporter.record(
87
+ ExampleResult.new(
88
+ id: example.id,
89
+ file: example.metadata[:file_path],
90
+ line: example.metadata[:line_number],
91
+ description: example.full_description,
92
+ verdict: verdict,
93
+ suggestions: suggestions,
94
+ declaration_validation: validation,
95
+ declaration: declaration,
96
+ example: example,
97
+ call_log: call_log,
98
+ executed_locations: executed_locations,
99
+ measured_locations: measured_locations,
100
+ unchecked_locations: unchecked_locations
101
+ )
102
+ )
103
+
104
+ emit_warning(example, verdict, suggestions, validation)
105
+ raise StrictCoverageError, verdict if fail_risky?(verdict, example)
106
+ end
107
+
108
+ def reset!
109
+ @configuration = Configuration.new
110
+ @reporter = Reporter.new
111
+ @coverage_probe = nil
112
+ @call_log_probe = nil
113
+ @dynamic_corpus = nil
114
+ @installed = false
115
+ end
116
+
117
+ private
118
+
119
+ def call_log_enabled?
120
+ configuration.suggest || configuration.mode == :checked || configuration.validate_declarations
121
+ end
122
+
123
+ def measured_locations(executed_locations, declaration, call_log)
124
+ return executed_locations unless configuration.mode == :checked
125
+
126
+ CheckedCoverage.new(configuration).filter(
127
+ executed_locations: executed_locations,
128
+ call_log: call_log,
129
+ expectation_object_ids: Thread.current[:rspec_covers_expectation_object_ids] || []
130
+ )
131
+ end
132
+
133
+ def suggestions_for(example, declaration, call_log, executed_locations)
134
+ return [] unless configuration.suggest
135
+ return [] if declaration.declared?
136
+
137
+ Tracer::Composite.new(configuration).suggest(
138
+ example: example,
139
+ call_log: call_log,
140
+ executed_locations: executed_locations,
141
+ corpus: dynamic_corpus
142
+ )
143
+ end
144
+
145
+ def validation_for(example, declaration, call_log, executed_locations)
146
+ return unless configuration.validate_declarations
147
+ return unless declaration.declared?
148
+
149
+ suggestions = Tracer::Composite.new(configuration).suggest(
150
+ example: example,
151
+ call_log: call_log,
152
+ executed_locations: executed_locations,
153
+ corpus: dynamic_corpus,
154
+ limit: nil
155
+ )
156
+
157
+ DeclarationValidator.new(configuration).call(
158
+ declaration: declaration,
159
+ suggestions: suggestions
160
+ )
161
+ end
162
+
163
+ def unchecked_locations_for(executed_locations, measured_locations)
164
+ return [] unless configuration.mode == :checked
165
+
166
+ Set.new(executed_locations).subtract(measured_locations).to_a
167
+ end
168
+
169
+ def fail_risky?(verdict, example)
170
+ verdict.risky? && configuration.risky == :fail && example.exception.nil?
171
+ end
172
+
173
+ def emit_warning(example, verdict, suggestions, validation)
174
+ messages = []
175
+ messages << verdict.message if verdict.reason == :undeclared_example && configuration.undeclared == :warn
176
+
177
+ suggestions.each do |suggestion|
178
+ messages << "#{example.metadata[:file_path]}:#{example.metadata[:line_number]} " \
179
+ "suggestion: covers: #{suggestion.label.inspect} " \
180
+ "(score #{suggestion.score.round(2)}: #{suggestion.reasons.join(" + ")})"
181
+ end
182
+
183
+ if validation && !validation.valid?
184
+ messages << "#{example.metadata[:file_path]}:#{example.metadata[:line_number]} " \
185
+ "covers declaration has weak traceability evidence: " \
186
+ "#{validation.unsupported_labels.join(", ")}"
187
+ end
188
+
189
+ messages.each { |message| rspec_message(message) }
190
+ end
191
+
192
+ def rspec_message(message)
193
+ if defined?(::RSpec) && ::RSpec.respond_to?(:configuration)
194
+ ::RSpec.configuration.reporter.message(message)
195
+ else
196
+ warn message
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ require_relative "covers/configuration"
204
+ require_relative "covers/code_location"
205
+ require_relative "covers/method_label"
206
+ require_relative "covers/source_range"
207
+ require_relative "covers/metadata_reader"
208
+ require_relative "covers/declaration"
209
+ require_relative "covers/declaration_validation"
210
+ require_relative "covers/method_entry"
211
+ require_relative "covers/probe/coverage_probe"
212
+ require_relative "covers/probe/call_log_probe"
213
+ require_relative "covers/strict_verdict"
214
+ require_relative "covers/checked_coverage"
215
+ require_relative "covers/static_method_scanner"
216
+ require_relative "covers/production_inventory"
217
+ require_relative "covers/tracer/composite"
218
+ require_relative "covers/reporter"
219
+ require_relative "covers/integration"
220
+ require_relative "covers/formatter"
221
+ require_relative "covers/evaluation"
222
+
223
+ module Rspec
224
+ Covers = ::RSpec::Covers unless const_defined?(:Covers, false)
225
+ end
@@ -0,0 +1,6 @@
1
+ module Minitest
2
+ module Covers
3
+ def self.configure: () { (::RSpec::Covers::Configuration) -> void } -> ::RSpec::Covers::Configuration
4
+ def self.install!: () -> void
5
+ end
6
+ end
@@ -0,0 +1,27 @@
1
+ module RSpec
2
+ module Covers
3
+ VERSION: String
4
+
5
+ def self.configure: (?untyped rspec_config) { (Configuration) -> void } -> Configuration
6
+ def self.install!: (untyped rspec_config) -> void
7
+
8
+ class Configuration
9
+ attr_accessor strict: bool
10
+ attr_accessor risky: Symbol
11
+ attr_accessor mode: Symbol
12
+ attr_accessor undeclared: Symbol
13
+ attr_accessor allowlist: Array[String]
14
+ attr_accessor suggest: bool
15
+ attr_accessor validate_declarations: bool
16
+ attr_accessor validation_threshold: Float
17
+ attr_accessor json_events: bool
18
+ attr_accessor root: String
19
+ attr_accessor production_paths: Array[String]
20
+ attr_accessor report_path: String?
21
+ end
22
+ end
23
+ end
24
+
25
+ module Rspec
26
+ Covers: singleton(::RSpec::Covers)
27
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec-covers
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rspec-core
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec-expectations
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: method_source
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: minitest
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '5.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '5.0'
68
+ description: Adds covers/uses metadata, per-example strict coverage checks, and traceability
69
+ suggestions for RSpec.
70
+ email:
71
+ - t.yudai92@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - LICENSE.txt
77
+ - README.md
78
+ - data/evaluation/ground_truth.example.json
79
+ - lib/minitest/covers.rb
80
+ - lib/rspec/covers.rb
81
+ - lib/rspec/covers/checked_coverage.rb
82
+ - lib/rspec/covers/code_location.rb
83
+ - lib/rspec/covers/configuration.rb
84
+ - lib/rspec/covers/declaration.rb
85
+ - lib/rspec/covers/declaration_validation.rb
86
+ - lib/rspec/covers/evaluation.rb
87
+ - lib/rspec/covers/formatter.rb
88
+ - lib/rspec/covers/integration.rb
89
+ - lib/rspec/covers/metadata_reader.rb
90
+ - lib/rspec/covers/method_entry.rb
91
+ - lib/rspec/covers/method_label.rb
92
+ - lib/rspec/covers/probe/call_log_probe.rb
93
+ - lib/rspec/covers/probe/coverage_probe.rb
94
+ - lib/rspec/covers/production_inventory.rb
95
+ - lib/rspec/covers/rake_task.rb
96
+ - lib/rspec/covers/reporter.rb
97
+ - lib/rspec/covers/source_range.rb
98
+ - lib/rspec/covers/static_method_scanner.rb
99
+ - lib/rspec/covers/strict_verdict.rb
100
+ - lib/rspec/covers/tracer/composite.rb
101
+ - lib/rspec/covers/tracer/dynamic.rb
102
+ - lib/rspec/covers/tracer/dynamic_corpus.rb
103
+ - lib/rspec/covers/tracer/lcba.rb
104
+ - lib/rspec/covers/tracer/nc.rb
105
+ - lib/rspec/covers/tracer/ncc.rb
106
+ - lib/rspec/covers/tracer/suggestion.rb
107
+ - lib/rspec/covers/tracer/tokenizer.rb
108
+ - lib/rspec/covers/version.rb
109
+ - sig/minitest/covers.rbs
110
+ - sig/rspec/covers.rbs
111
+ homepage: https://github.com/ydah/rspec-covers
112
+ licenses:
113
+ - MIT
114
+ metadata:
115
+ homepage_uri: https://github.com/ydah/rspec-covers
116
+ source_code_uri: https://github.com/ydah/rspec-covers
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 3.2.0
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubygems_version: 4.0.6
132
+ specification_version: 4
133
+ summary: RSpec coverage intent metadata and strict coverage checks.
134
+ test_files: []