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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +202 -0
- data/data/evaluation/ground_truth.example.json +26 -0
- data/lib/minitest/covers.rb +133 -0
- data/lib/rspec/covers/checked_coverage.rb +27 -0
- data/lib/rspec/covers/code_location.rb +49 -0
- data/lib/rspec/covers/configuration.rb +135 -0
- data/lib/rspec/covers/declaration.rb +199 -0
- data/lib/rspec/covers/declaration_validation.rb +65 -0
- data/lib/rspec/covers/evaluation.rb +197 -0
- data/lib/rspec/covers/formatter.rb +70 -0
- data/lib/rspec/covers/integration.rb +54 -0
- data/lib/rspec/covers/metadata_reader.rb +34 -0
- data/lib/rspec/covers/method_entry.rb +19 -0
- data/lib/rspec/covers/method_label.rb +37 -0
- data/lib/rspec/covers/probe/call_log_probe.rb +132 -0
- data/lib/rspec/covers/probe/coverage_probe.rb +57 -0
- data/lib/rspec/covers/production_inventory.rb +90 -0
- data/lib/rspec/covers/rake_task.rb +63 -0
- data/lib/rspec/covers/reporter.rb +162 -0
- data/lib/rspec/covers/source_range.rb +37 -0
- data/lib/rspec/covers/static_method_scanner.rb +184 -0
- data/lib/rspec/covers/strict_verdict.rb +107 -0
- data/lib/rspec/covers/tracer/composite.rb +73 -0
- data/lib/rspec/covers/tracer/dynamic.rb +27 -0
- data/lib/rspec/covers/tracer/dynamic_corpus.rb +43 -0
- data/lib/rspec/covers/tracer/lcba.rb +17 -0
- data/lib/rspec/covers/tracer/nc.rb +36 -0
- data/lib/rspec/covers/tracer/ncc.rb +23 -0
- data/lib/rspec/covers/tracer/suggestion.rb +17 -0
- data/lib/rspec/covers/tracer/tokenizer.rb +18 -0
- data/lib/rspec/covers/version.rb +11 -0
- data/lib/rspec/covers.rb +225 -0
- data/sig/minitest/covers.rbs +6 -0
- data/sig/rspec/covers.rbs +27 -0
- metadata +134 -0
data/lib/rspec/covers.rb
ADDED
|
@@ -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,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: []
|