rubocop-rspec-guide 0.2.1 → 0.4.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -6
  3. data/.yardopts +9 -0
  4. data/CHANGELOG.md +92 -0
  5. data/CONTRIBUTING.md +358 -0
  6. data/INTEGRATION_TESTING.md +324 -0
  7. data/README.md +443 -16
  8. data/Rakefile +49 -0
  9. data/benchmark/README.md +349 -0
  10. data/benchmark/baseline_v0.3.1.txt +67 -0
  11. data/benchmark/baseline_v0.4.0.txt +167 -0
  12. data/benchmark/benchmark_helper.rb +92 -0
  13. data/benchmark/compare_versions.rb +136 -0
  14. data/benchmark/cops_benchmark.rb +428 -0
  15. data/benchmark/cops_performance.rb +109 -0
  16. data/benchmark/quick_comparison.rb +58 -0
  17. data/benchmark/quick_invariant_bench.rb +52 -0
  18. data/benchmark/rspec_base_integration.rb +86 -0
  19. data/benchmark/save_baseline.rb +18 -0
  20. data/benchmark/scalability_benchmark.rb +181 -0
  21. data/config/default.yml +43 -2
  22. data/config/obsoletion.yml +6 -0
  23. data/lib/rubocop/cop/factory_bot_guide/dynamic_attribute_evaluation.rb +193 -0
  24. data/lib/rubocop/cop/factory_bot_guide/dynamic_attributes_for_time_and_random.rb +10 -106
  25. data/lib/rubocop/cop/rspec_guide/characteristics_and_contexts.rb +13 -78
  26. data/lib/rubocop/cop/rspec_guide/context_setup.rb +81 -30
  27. data/lib/rubocop/cop/rspec_guide/duplicate_before_hooks.rb +89 -22
  28. data/lib/rubocop/cop/rspec_guide/duplicate_let_values.rb +91 -22
  29. data/lib/rubocop/cop/rspec_guide/happy_path_first.rb +52 -21
  30. data/lib/rubocop/cop/rspec_guide/invariant_examples.rb +60 -19
  31. data/lib/rubocop/cop/rspec_guide/minimum_behavioral_coverage.rb +165 -0
  32. data/lib/rubocop/rspec/guide/inject.rb +26 -0
  33. data/lib/rubocop/rspec/guide/plugin.rb +45 -0
  34. data/lib/rubocop/rspec/guide/version.rb +1 -1
  35. data/lib/rubocop-rspec-guide.rb +4 -0
  36. metadata +49 -1
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Benchmark to compare performance before and after RSpec::Base integration
5
+ # This shows the impact of using rubocop-rspec Language API
6
+
7
+ require "benchmark"
8
+ require "rubocop"
9
+
10
+ SAMPLE_CODE = <<~RUBY
11
+ describe 'UserService' do
12
+ context 'when user is admin' do
13
+ let(:user) { create(:user, :admin) }
14
+ let(:service) { UserService.new(user) }
15
+
16
+ before { setup_admin_permissions }
17
+
18
+ it 'has admin access' do
19
+ expect(service.admin?).to be true
20
+ end
21
+ end
22
+
23
+ context 'when user is regular' do
24
+ let(:user) { create(:user) }
25
+ let(:service) { UserService.new(user) }
26
+
27
+ before { setup_admin_permissions }
28
+
29
+ it 'does not have admin access' do
30
+ expect(service.admin?).to be false
31
+ end
32
+ end
33
+ end
34
+ RUBY
35
+
36
+ puts "=" * 80
37
+ puts "Performance Comparison: Before vs After RSpec::Base Integration"
38
+ puts "=" * 80
39
+ puts
40
+
41
+ source = RuboCop::ProcessedSource.new(SAMPLE_CODE, RUBY_VERSION.to_f)
42
+
43
+ config = RuboCop::Config.new(
44
+ {
45
+ "RSpec" => {
46
+ "Enabled" => true,
47
+ "Language" => {
48
+ "ExampleGroups" => {
49
+ "Regular" => %w[describe context feature example_group],
50
+ "Skipped" => %w[xdescribe xcontext xfeature],
51
+ "Focused" => %w[fdescribe fcontext ffeature]
52
+ },
53
+ "Examples" => {
54
+ "Regular" => %w[it specify example scenario its],
55
+ "Focused" => %w[fit fspecify fexample fscenario focus],
56
+ "Skipped" => %w[xit xspecify xexample xscenario skip],
57
+ "Pending" => %w[pending]
58
+ },
59
+ "Helpers" => %w[let let! let_it_be let_it_be!],
60
+ "Hooks" => %w[prepend_before before append_before around prepend_after after append_after],
61
+ "Subjects" => %w[subject subject!]
62
+ }
63
+ }
64
+ }
65
+ )
66
+
67
+ # Test individual matcher performance
68
+ iterations = 10_000
69
+
70
+ puts "Testing node matcher performance (#{iterations} iterations):"
71
+ puts "-" * 80
72
+
73
+ require_relative "../lib/rubocop-rspec-guide"
74
+
75
+ # Initialize RSpec Language config
76
+ RuboCop::RSpec::Language.config = config["RSpec"]["Language"]
77
+
78
+ Benchmark.bm(45) do |x|
79
+ # Test let? matcher
80
+ cop = RuboCop::Cop::RSpecGuide::DuplicateLetValues.new(config)
81
+ let_nodes = []
82
+
83
+ source.ast.each_node(:block) do |node|
84
+ let_nodes << node if cop.let?(node)
85
+ end
86
+
87
+ x.report("let? matcher (finds #{let_nodes.size} nodes)") do
88
+ iterations.times do
89
+ source.ast.each_node(:block) do |node|
90
+ cop.let?(node)
91
+ end
92
+ end
93
+ end
94
+
95
+ # Test example_group? matcher
96
+ cop2 = RuboCop::Cop::RSpecGuide::MinimumBehavioralCoverage.new(config)
97
+ group_nodes = []
98
+
99
+ source.ast.each_node(:block) do |node|
100
+ group_nodes << node if cop2.example_group?(node)
101
+ end
102
+
103
+ x.report("example_group? matcher (finds #{group_nodes.size} nodes)") do
104
+ iterations.times do
105
+ source.ast.each_node(:block) do |node|
106
+ cop2.example_group?(node)
107
+ end
108
+ end
109
+ end
110
+
111
+ # Test hook? matcher
112
+ cop3 = RuboCop::Cop::RSpecGuide::DuplicateBeforeHooks.new(config)
113
+ hook_nodes = []
114
+
115
+ source.ast.each_node(:block) do |node|
116
+ hook_nodes << node if cop3.hook?(node)
117
+ end
118
+
119
+ x.report("hook? matcher (finds #{hook_nodes.size} nodes)") do
120
+ iterations.times do
121
+ source.ast.each_node(:block) do |node|
122
+ cop3.hook?(node)
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ puts
129
+ puts "=" * 80
130
+ puts "Summary:"
131
+ puts "=" * 80
132
+ puts "✅ Current implementation uses RuboCop::Cop::RSpec::Base"
133
+ puts "✅ Leverages well-tested rubocop-rspec Language API"
134
+ puts "✅ Supports let_it_be and let_it_be! out of the box"
135
+ puts "✅ Consistent with rubocop-rspec ecosystem"
136
+ puts "=" * 80
@@ -0,0 +1,428 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "benchmark_helper"
5
+
6
+ # Benchmark all RSpecGuide cops
7
+ puts "=" * 80
8
+ puts "RuboCop RSpec Guide - Cops Performance Benchmark"
9
+ puts "=" * 80
10
+ puts ""
11
+
12
+ # Configure benchmark timing based on environment
13
+ # FULL_BENCHMARK=1 - Full mode: longer warmup/measurement for accuracy (~5 minutes)
14
+ # Default - Quick mode: shorter warmup/measurement for fast feedback (~1 minute)
15
+ if ENV["FULL_BENCHMARK"]
16
+ WARMUP_TIME = 2
17
+ MEASUREMENT_TIME = 5
18
+ puts "Mode: FULL (accurate measurements, ~5 minutes)"
19
+ else
20
+ WARMUP_TIME = 1
21
+ MEASUREMENT_TIME = 2
22
+ puts "Mode: QUICK (fast feedback, ~1 minute)"
23
+ puts "Tip: Use FULL_BENCHMARK=1 for more accurate measurements"
24
+ end
25
+ puts ""
26
+
27
+ # Benchmark MinimumBehavioralCoverage
28
+ Benchmark.ips do |x|
29
+ x.config(time: MEASUREMENT_TIME, warmup: WARMUP_TIME)
30
+
31
+ source_with_violation = <<~RUBY
32
+ RSpec.describe User do
33
+ context "when user is valid" do
34
+ it "saves successfully" do
35
+ expect(user.save).to be true
36
+ end
37
+ end
38
+ end
39
+ RUBY
40
+
41
+ source_without_violation = <<~RUBY
42
+ RSpec.describe User do
43
+ context "when user is valid" do
44
+ it "saves successfully" do
45
+ expect(user.save).to be true
46
+ end
47
+ end
48
+
49
+ context "when user is invalid" do
50
+ it "does not save" do
51
+ expect(user.save).to be false
52
+ end
53
+ end
54
+ end
55
+ RUBY
56
+
57
+ x.report("MinimumBehavioralCoverage (with violation)") do
58
+ BenchmarkHelper.run_cop(
59
+ RuboCop::Cop::RSpecGuide::MinimumBehavioralCoverage,
60
+ source_with_violation
61
+ )
62
+ end
63
+
64
+ x.report("MinimumBehavioralCoverage (without violation)") do
65
+ BenchmarkHelper.run_cop(
66
+ RuboCop::Cop::RSpecGuide::MinimumBehavioralCoverage,
67
+ source_without_violation
68
+ )
69
+ end
70
+
71
+ x.compare!
72
+ end
73
+
74
+ puts "\n"
75
+
76
+ # Benchmark HappyPathFirst
77
+ Benchmark.ips do |x|
78
+ x.config(time: MEASUREMENT_TIME, warmup: WARMUP_TIME)
79
+
80
+ source_with_violation = <<~RUBY
81
+ RSpec.describe User do
82
+ context "when email is invalid" do
83
+ it "returns error" do
84
+ expect(user.valid?).to be false
85
+ end
86
+ end
87
+
88
+ context "when email is valid" do
89
+ it "is valid" do
90
+ expect(user.valid?).to be true
91
+ end
92
+ end
93
+ end
94
+ RUBY
95
+
96
+ source_without_violation = <<~RUBY
97
+ RSpec.describe User do
98
+ context "when email is valid" do
99
+ it "is valid" do
100
+ expect(user.valid?).to be true
101
+ end
102
+ end
103
+
104
+ context "when email is invalid" do
105
+ it "returns error" do
106
+ expect(user.valid?).to be false
107
+ end
108
+ end
109
+ end
110
+ RUBY
111
+
112
+ x.report("HappyPathFirst (with violation)") do
113
+ BenchmarkHelper.run_cop(
114
+ RuboCop::Cop::RSpecGuide::HappyPathFirst,
115
+ source_with_violation
116
+ )
117
+ end
118
+
119
+ x.report("HappyPathFirst (without violation)") do
120
+ BenchmarkHelper.run_cop(
121
+ RuboCop::Cop::RSpecGuide::HappyPathFirst,
122
+ source_without_violation
123
+ )
124
+ end
125
+
126
+ x.compare!
127
+ end
128
+
129
+ puts "\n"
130
+
131
+ # Benchmark ContextSetup
132
+ Benchmark.ips do |x|
133
+ x.config(time: MEASUREMENT_TIME, warmup: WARMUP_TIME)
134
+
135
+ source_with_violation = <<~RUBY
136
+ RSpec.describe User do
137
+ context "when user is admin" do
138
+ it "has admin privileges" do
139
+ user = create(:user, :admin)
140
+ expect(user.admin?).to be true
141
+ end
142
+ end
143
+ end
144
+ RUBY
145
+
146
+ source_without_violation = <<~RUBY
147
+ RSpec.describe User do
148
+ context "when user is admin" do
149
+ let(:user) { create(:user, :admin) }
150
+
151
+ it "has admin privileges" do
152
+ expect(user.admin?).to be true
153
+ end
154
+ end
155
+ end
156
+ RUBY
157
+
158
+ x.report("ContextSetup (with violation)") do
159
+ BenchmarkHelper.run_cop(
160
+ RuboCop::Cop::RSpecGuide::ContextSetup,
161
+ source_with_violation
162
+ )
163
+ end
164
+
165
+ x.report("ContextSetup (without violation)") do
166
+ BenchmarkHelper.run_cop(
167
+ RuboCop::Cop::RSpecGuide::ContextSetup,
168
+ source_without_violation
169
+ )
170
+ end
171
+
172
+ x.compare!
173
+ end
174
+
175
+ puts "\n"
176
+
177
+ # Benchmark DuplicateLetValues
178
+ Benchmark.ips do |x|
179
+ x.config(time: MEASUREMENT_TIME, warmup: WARMUP_TIME)
180
+
181
+ source_with_violation = <<~RUBY
182
+ RSpec.describe Calculator do
183
+ context "with positive numbers" do
184
+ let(:value) { 42 }
185
+
186
+ it "works" do
187
+ expect(value).to eq(42)
188
+ end
189
+ end
190
+
191
+ context "with negative numbers" do
192
+ let(:value) { 42 }
193
+
194
+ it "works differently" do
195
+ expect(value).to eq(42)
196
+ end
197
+ end
198
+ end
199
+ RUBY
200
+
201
+ source_without_violation = <<~RUBY
202
+ RSpec.describe Calculator do
203
+ let(:default_value) { 42 }
204
+
205
+ context "with positive numbers" do
206
+ let(:value) { default_value }
207
+
208
+ it "works" do
209
+ expect(value).to eq(42)
210
+ end
211
+ end
212
+
213
+ context "with negative numbers" do
214
+ let(:value) { -default_value }
215
+
216
+ it "works differently" do
217
+ expect(value).to eq(-42)
218
+ end
219
+ end
220
+ end
221
+ RUBY
222
+
223
+ x.report("DuplicateLetValues (with violation)") do
224
+ BenchmarkHelper.run_cop(
225
+ RuboCop::Cop::RSpecGuide::DuplicateLetValues,
226
+ source_with_violation
227
+ )
228
+ end
229
+
230
+ x.report("DuplicateLetValues (without violation)") do
231
+ BenchmarkHelper.run_cop(
232
+ RuboCop::Cop::RSpecGuide::DuplicateLetValues,
233
+ source_without_violation
234
+ )
235
+ end
236
+
237
+ x.compare!
238
+ end
239
+
240
+ puts "\n"
241
+
242
+ # Benchmark DuplicateBeforeHooks
243
+ Benchmark.ips do |x|
244
+ x.config(time: MEASUREMENT_TIME, warmup: WARMUP_TIME)
245
+
246
+ source_with_violation = <<~RUBY
247
+ RSpec.describe Service do
248
+ context "with feature enabled" do
249
+ before do
250
+ setup_database
251
+ end
252
+
253
+ it "works" do
254
+ expect(service.call).to be_success
255
+ end
256
+ end
257
+
258
+ context "with feature disabled" do
259
+ before do
260
+ setup_database
261
+ end
262
+
263
+ it "still works" do
264
+ expect(service.call).to be_success
265
+ end
266
+ end
267
+ end
268
+ RUBY
269
+
270
+ source_without_violation = <<~RUBY
271
+ RSpec.describe Service do
272
+ before do
273
+ setup_database
274
+ end
275
+
276
+ context "with feature enabled" do
277
+ it "works" do
278
+ expect(service.call).to be_success
279
+ end
280
+ end
281
+
282
+ context "with feature disabled" do
283
+ it "still works" do
284
+ expect(service.call).to be_success
285
+ end
286
+ end
287
+ end
288
+ RUBY
289
+
290
+ x.report("DuplicateBeforeHooks (with violation)") do
291
+ BenchmarkHelper.run_cop(
292
+ RuboCop::Cop::RSpecGuide::DuplicateBeforeHooks,
293
+ source_with_violation
294
+ )
295
+ end
296
+
297
+ x.report("DuplicateBeforeHooks (without violation)") do
298
+ BenchmarkHelper.run_cop(
299
+ RuboCop::Cop::RSpecGuide::DuplicateBeforeHooks,
300
+ source_without_violation
301
+ )
302
+ end
303
+
304
+ x.compare!
305
+ end
306
+
307
+ puts "\n"
308
+
309
+ # Benchmark InvariantExamples
310
+ Benchmark.ips do |x|
311
+ x.config(time: MEASUREMENT_TIME, warmup: WARMUP_TIME)
312
+
313
+ source_with_violation = <<~RUBY
314
+ RSpec.describe Calculator do
315
+ context "with integers" do
316
+ it "adds numbers" do
317
+ expect(calculator.add(1, 2)).to eq(3)
318
+ end
319
+
320
+ it "returns numeric result" do
321
+ expect(calculator.add(1, 2)).to be_a(Numeric)
322
+ end
323
+ end
324
+
325
+ context "with floats" do
326
+ it "adds numbers" do
327
+ expect(calculator.add(1.5, 2.5)).to eq(4.0)
328
+ end
329
+
330
+ it "returns numeric result" do
331
+ expect(calculator.add(1.5, 2.5)).to be_a(Numeric)
332
+ end
333
+ end
334
+ end
335
+ RUBY
336
+
337
+ source_without_violation = <<~RUBY
338
+ RSpec.describe Calculator do
339
+ shared_examples "returns numeric result" do
340
+ it "returns numeric result" do
341
+ expect(result).to be_a(Numeric)
342
+ end
343
+ end
344
+
345
+ context "with integers" do
346
+ let(:result) { calculator.add(1, 2) }
347
+
348
+ it "adds numbers" do
349
+ expect(result).to eq(3)
350
+ end
351
+
352
+ include_examples "returns numeric result"
353
+ end
354
+
355
+ context "with floats" do
356
+ let(:result) { calculator.add(1.5, 2.5) }
357
+
358
+ it "adds numbers" do
359
+ expect(result).to eq(4.0)
360
+ end
361
+
362
+ include_examples "returns numeric result"
363
+ end
364
+ end
365
+ RUBY
366
+
367
+ x.report("InvariantExamples (with violation)") do
368
+ BenchmarkHelper.run_cop(
369
+ RuboCop::Cop::RSpecGuide::InvariantExamples,
370
+ source_with_violation
371
+ )
372
+ end
373
+
374
+ x.report("InvariantExamples (without violation)") do
375
+ BenchmarkHelper.run_cop(
376
+ RuboCop::Cop::RSpecGuide::InvariantExamples,
377
+ source_without_violation
378
+ )
379
+ end
380
+
381
+ x.compare!
382
+ end
383
+
384
+ puts "\n"
385
+
386
+ # Benchmark DynamicAttributeEvaluation
387
+ Benchmark.ips do |x|
388
+ x.config(time: MEASUREMENT_TIME, warmup: WARMUP_TIME)
389
+
390
+ source_with_violation = <<~RUBY
391
+ FactoryBot.define do
392
+ factory :user do
393
+ created_at { Time.current }
394
+ random_number { rand(1..100) }
395
+ end
396
+ end
397
+ RUBY
398
+
399
+ source_without_violation = <<~RUBY
400
+ FactoryBot.define do
401
+ factory :user do
402
+ created_at { -> { Time.current } }
403
+ random_number { -> { rand(1..100) } }
404
+ end
405
+ end
406
+ RUBY
407
+
408
+ x.report("DynamicAttributeEvaluation (with violation)") do
409
+ BenchmarkHelper.run_cop(
410
+ RuboCop::Cop::FactoryBotGuide::DynamicAttributeEvaluation,
411
+ source_with_violation
412
+ )
413
+ end
414
+
415
+ x.report("DynamicAttributeEvaluation (without violation)") do
416
+ BenchmarkHelper.run_cop(
417
+ RuboCop::Cop::FactoryBotGuide::DynamicAttributeEvaluation,
418
+ source_without_violation
419
+ )
420
+ end
421
+
422
+ x.compare!
423
+ end
424
+
425
+ puts "\n"
426
+ puts "=" * 80
427
+ puts "Benchmark completed!"
428
+ puts "=" * 80
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "benchmark"
5
+ require "rubocop"
6
+ require_relative "../lib/rubocop-rspec-guide"
7
+
8
+ # Sample RSpec code for benchmarking
9
+ SAMPLE_CODE = <<~RUBY
10
+ describe 'UserService' do
11
+ context 'when user is admin' do
12
+ let(:user) { create(:user, :admin) }
13
+ let(:service) { UserService.new(user) }
14
+
15
+ before { setup_admin_permissions }
16
+
17
+ it 'has admin access' do
18
+ expect(service.admin?).to be true
19
+ end
20
+
21
+ it 'can modify settings' do
22
+ expect(service.can_modify_settings?).to be true
23
+ end
24
+ end
25
+
26
+ context 'when user is regular' do
27
+ let(:user) { create(:user) }
28
+ let(:service) { UserService.new(user) }
29
+
30
+ before { setup_admin_permissions }
31
+
32
+ it 'does not have admin access' do
33
+ expect(service.admin?).to be false
34
+ end
35
+
36
+ it 'cannot modify settings' do
37
+ expect(service.can_modify_settings?).to be false
38
+ end
39
+ end
40
+ end
41
+ RUBY
42
+
43
+ puts "=" * 80
44
+ puts "RuboCop RSpec Cops Performance Benchmark"
45
+ puts "=" * 80
46
+ puts "Sample code: #{SAMPLE_CODE.lines.count} lines"
47
+ puts "Ruby version: #{RUBY_VERSION}"
48
+ puts "RuboCop version: #{RuboCop::Version.version}"
49
+ puts "=" * 80
50
+ puts
51
+
52
+ # Parse the code once
53
+ source = RuboCop::ProcessedSource.new(SAMPLE_CODE, RUBY_VERSION.to_f)
54
+
55
+ # Create config
56
+ config = RuboCop::Config.new(
57
+ {
58
+ "RSpec" => {
59
+ "Enabled" => true,
60
+ "Language" => {
61
+ "ExampleGroups" => {
62
+ "Regular" => %w[describe context]
63
+ },
64
+ "Examples" => {
65
+ "Regular" => %w[it]
66
+ },
67
+ "Helpers" => %w[let let!],
68
+ "Hooks" => %w[before after],
69
+ "Subjects" => %w[subject]
70
+ }
71
+ }
72
+ }
73
+ )
74
+
75
+ cops = [
76
+ RuboCop::Cop::RSpecGuide::MinimumBehavioralCoverage,
77
+ RuboCop::Cop::RSpecGuide::HappyPathFirst,
78
+ RuboCop::Cop::RSpecGuide::ContextSetup,
79
+ RuboCop::Cop::RSpecGuide::DuplicateLetValues,
80
+ RuboCop::Cop::RSpecGuide::DuplicateBeforeHooks,
81
+ RuboCop::Cop::RSpecGuide::InvariantExamples
82
+ ]
83
+
84
+ iterations = 1000
85
+
86
+ Benchmark.bm(40) do |x|
87
+ cops.each do |cop_class|
88
+ x.report(cop_class.badge.to_s) do
89
+ iterations.times do
90
+ cop = cop_class.new(config)
91
+ commissioner = RuboCop::Cop::Commissioner.new([cop], [], raise_error: false)
92
+ commissioner.investigate(source)
93
+ end
94
+ end
95
+ end
96
+
97
+ x.report("All 6 cops together") do
98
+ iterations.times do
99
+ cop_instances = cops.map { |c| c.new(config) }
100
+ commissioner = RuboCop::Cop::Commissioner.new(cop_instances, [], raise_error: false)
101
+ commissioner.investigate(source)
102
+ end
103
+ end
104
+ end
105
+
106
+ puts
107
+ puts "=" * 80
108
+ puts "Benchmark complete! (#{iterations} iterations per cop)"
109
+ puts "=" * 80