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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -6
- data/.yardopts +9 -0
- data/CHANGELOG.md +92 -0
- data/CONTRIBUTING.md +358 -0
- data/INTEGRATION_TESTING.md +324 -0
- data/README.md +443 -16
- data/Rakefile +49 -0
- data/benchmark/README.md +349 -0
- data/benchmark/baseline_v0.3.1.txt +67 -0
- data/benchmark/baseline_v0.4.0.txt +167 -0
- data/benchmark/benchmark_helper.rb +92 -0
- data/benchmark/compare_versions.rb +136 -0
- data/benchmark/cops_benchmark.rb +428 -0
- data/benchmark/cops_performance.rb +109 -0
- data/benchmark/quick_comparison.rb +58 -0
- data/benchmark/quick_invariant_bench.rb +52 -0
- data/benchmark/rspec_base_integration.rb +86 -0
- data/benchmark/save_baseline.rb +18 -0
- data/benchmark/scalability_benchmark.rb +181 -0
- data/config/default.yml +43 -2
- data/config/obsoletion.yml +6 -0
- data/lib/rubocop/cop/factory_bot_guide/dynamic_attribute_evaluation.rb +193 -0
- data/lib/rubocop/cop/factory_bot_guide/dynamic_attributes_for_time_and_random.rb +10 -106
- data/lib/rubocop/cop/rspec_guide/characteristics_and_contexts.rb +13 -78
- data/lib/rubocop/cop/rspec_guide/context_setup.rb +81 -30
- data/lib/rubocop/cop/rspec_guide/duplicate_before_hooks.rb +89 -22
- data/lib/rubocop/cop/rspec_guide/duplicate_let_values.rb +91 -22
- data/lib/rubocop/cop/rspec_guide/happy_path_first.rb +52 -21
- data/lib/rubocop/cop/rspec_guide/invariant_examples.rb +60 -19
- data/lib/rubocop/cop/rspec_guide/minimum_behavioral_coverage.rb +165 -0
- data/lib/rubocop/rspec/guide/inject.rb +26 -0
- data/lib/rubocop/rspec/guide/plugin.rb +45 -0
- data/lib/rubocop/rspec/guide/version.rb +1 -1
- data/lib/rubocop-rspec-guide.rb +4 -0
- metadata +49 -1
data/benchmark/README.md
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# Performance Benchmarks
|
|
2
|
+
|
|
3
|
+
This directory contains performance benchmarks for RuboCop RSpec Guide cops.
|
|
4
|
+
|
|
5
|
+
## Current Performance (v0.3.1)
|
|
6
|
+
|
|
7
|
+
Based on actual measurements on Ruby 3.4.7 (2025-10-30):
|
|
8
|
+
|
|
9
|
+
### Typical Files (10-20 lines, simple RSpec examples)
|
|
10
|
+
|
|
11
|
+
| Cop Name | Iterations/sec | Time per file | Status |
|
|
12
|
+
|----------|---------------|---------------|---------|
|
|
13
|
+
| MinimumBehavioralCoverage | 2,457 i/s | 407 μs | ⚡ Excellent |
|
|
14
|
+
| DynamicAttributeEvaluation | 2,423 i/s | 413 μs | ⚡ Excellent |
|
|
15
|
+
| ContextSetup | 2,040 i/s | 490 μs | ⚡ Excellent |
|
|
16
|
+
| HappyPathFirst | 2,003 i/s | 499 μs | ⚡ Excellent |
|
|
17
|
+
| DuplicateBeforeHooks | 1,626 i/s | 615 μs | ✅ Good |
|
|
18
|
+
| InvariantExamples | 1,504 i/s | 665 μs | ✅ Good |
|
|
19
|
+
| DuplicateLetValues | 1,499 i/s | 667 μs | ✅ Good |
|
|
20
|
+
|
|
21
|
+
**Real-world impact:** At 2,000 i/s average, scanning 100 typical spec files takes only **0.05 seconds** ⚡
|
|
22
|
+
|
|
23
|
+
### Large Files (Scalability)
|
|
24
|
+
|
|
25
|
+
| File Size | Iterations/sec | Time per file | Lines | Contexts |
|
|
26
|
+
|-----------|---------------|---------------|-------|----------|
|
|
27
|
+
| Small | 357 i/s | 2.8 ms | 87 | 5 |
|
|
28
|
+
| Medium | 167 i/s | 6.0 ms | 172 | 10 |
|
|
29
|
+
| Large | 73 i/s | 13.6 ms | 342 | 20 |
|
|
30
|
+
|
|
31
|
+
**Scalability:** Complexity is **O(n) - linear** ✅
|
|
32
|
+
- Doubling file size approximately doubles processing time
|
|
33
|
+
- No exponential degradation observed
|
|
34
|
+
- Slight overhead: 2x size = 2.1x time (expected behavior)
|
|
35
|
+
|
|
36
|
+
**Real-world impact:** At 100 i/s average for large files, scanning 100 large spec files takes **1 second** - acceptable for CI/CD.
|
|
37
|
+
|
|
38
|
+
## Performance Baseline
|
|
39
|
+
|
|
40
|
+
### Target Performance (based on measurements)
|
|
41
|
+
|
|
42
|
+
**Typical files (10-20 lines):**
|
|
43
|
+
- Expected: **1,500-2,500 i/s** (0.4-0.7 ms per file)
|
|
44
|
+
- Warning threshold: **< 1,000 i/s** (investigate if below)
|
|
45
|
+
- Critical threshold: **< 500 i/s** (requires optimization)
|
|
46
|
+
|
|
47
|
+
**Large files (300+ lines, 20+ contexts):**
|
|
48
|
+
- Expected: **70-150 i/s** (7-14 ms per file)
|
|
49
|
+
- Warning threshold: **< 50 i/s**
|
|
50
|
+
- Critical threshold: **< 25 i/s**
|
|
51
|
+
|
|
52
|
+
**Scalability:**
|
|
53
|
+
- Complexity: **O(n)** - linear scaling required
|
|
54
|
+
- Doubling file size should approximately double processing time
|
|
55
|
+
- Warning: > 2.5x increase (may indicate O(n²) behavior)
|
|
56
|
+
- Critical: > 5x increase (algorithmic problem)
|
|
57
|
+
|
|
58
|
+
**Memory usage:**
|
|
59
|
+
- Target: < 10 MB per large file (500+ lines)
|
|
60
|
+
- Warning: > 20 MB
|
|
61
|
+
- Critical: > 50 MB (memory leak suspected)
|
|
62
|
+
|
|
63
|
+
### Why These Numbers?
|
|
64
|
+
|
|
65
|
+
These baselines are established from actual measurements on production-quality code:
|
|
66
|
+
|
|
67
|
+
1. **1,500-2,500 i/s for typical files** allows scanning large codebases quickly
|
|
68
|
+
- 1,000 files = 0.5 seconds at 2,000 i/s
|
|
69
|
+
- This keeps CI/CD pipelines fast
|
|
70
|
+
|
|
71
|
+
2. **70-150 i/s for large files** is acceptable given complexity
|
|
72
|
+
- Large files are rare in well-structured codebases
|
|
73
|
+
- Still allows 100 large files in ~1 second
|
|
74
|
+
|
|
75
|
+
3. **O(n) complexity** prevents exponential slowdowns
|
|
76
|
+
- Critical for maintaining performance as files grow
|
|
77
|
+
- AST traversal is inherently O(n)
|
|
78
|
+
|
|
79
|
+
## Requirements
|
|
80
|
+
|
|
81
|
+
Install benchmark dependencies:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
bundle install
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Running Benchmarks
|
|
88
|
+
|
|
89
|
+
### Quick Benchmark (recommended for development)
|
|
90
|
+
|
|
91
|
+
Run a fast benchmark for immediate feedback (~1 minute):
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
rake benchmark:quick
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Or directly:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
bundle exec ruby benchmark/cops_benchmark.rb
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This uses shorter warmup (1s) and measurement (2s) times for quick feedback during development.
|
|
104
|
+
|
|
105
|
+
### Full Benchmark (for accurate measurements)
|
|
106
|
+
|
|
107
|
+
Run comprehensive benchmark with longer measurement times (~3 minutes):
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
FULL_BENCHMARK=1 rake benchmark
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This uses longer warmup (2s) and measurement (5s) times for more accurate statistical analysis.
|
|
114
|
+
|
|
115
|
+
### All Cops Performance
|
|
116
|
+
|
|
117
|
+
Benchmark each cop individually:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
bundle exec ruby benchmark/cops_benchmark.rb
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This will benchmark each cop with both violation and non-violation cases, showing:
|
|
124
|
+
- Iterations per second (i/s)
|
|
125
|
+
- Microseconds per iteration (μs/i)
|
|
126
|
+
- Comparison between violation and non-violation cases
|
|
127
|
+
- Performance characteristics of each cop
|
|
128
|
+
|
|
129
|
+
### Scalability Testing
|
|
130
|
+
|
|
131
|
+
Test how cops perform with different file sizes:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
bundle exec ruby benchmark/scalability_benchmark.rb
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
This benchmark tests:
|
|
138
|
+
- Performance with increasing number of contexts (5, 10, 20, 50)
|
|
139
|
+
- Performance with increasing number of examples (10, 25, 50, 100)
|
|
140
|
+
- Performance with nested contexts (3, 5, 10, 15 levels)
|
|
141
|
+
- Performance with duplicate examples (2, 5, 10, 15 duplicates)
|
|
142
|
+
- Memory usage for large files
|
|
143
|
+
|
|
144
|
+
### Using Rake Tasks
|
|
145
|
+
|
|
146
|
+
You can also run benchmarks using Rake:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Run quick benchmarks (recommended)
|
|
150
|
+
rake benchmark:quick
|
|
151
|
+
|
|
152
|
+
# Run all benchmarks (full mode)
|
|
153
|
+
rake benchmark
|
|
154
|
+
|
|
155
|
+
# Run only cops benchmark
|
|
156
|
+
rake benchmark:cops
|
|
157
|
+
|
|
158
|
+
# Run only scalability benchmark
|
|
159
|
+
rake benchmark:scalability
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Understanding Results
|
|
163
|
+
|
|
164
|
+
### Iterations per Second (i/s)
|
|
165
|
+
|
|
166
|
+
Higher is better. This shows how many times per second the cop can analyze the given source code.
|
|
167
|
+
|
|
168
|
+
Example output:
|
|
169
|
+
```
|
|
170
|
+
MinimumBehavioralCoverage (with violation) 2.457k (± 1.7%) i/s (407.05 μs/i)
|
|
171
|
+
MinimumBehavioralCoverage (without violation) 1.683k (± 1.4%) i/s (594.34 μs/i)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
This means:
|
|
175
|
+
- **2.457k i/s** = 2,457 iterations per second
|
|
176
|
+
- **(407.05 μs/i)** = 0.407 milliseconds per file
|
|
177
|
+
- **(± 1.7%)** = standard deviation (lower is more consistent)
|
|
178
|
+
|
|
179
|
+
At 2,457 i/s, the cop can analyze:
|
|
180
|
+
- 100 files in 0.04 seconds
|
|
181
|
+
- 1,000 files in 0.4 seconds
|
|
182
|
+
- 10,000 files in 4 seconds
|
|
183
|
+
|
|
184
|
+
### Comparison
|
|
185
|
+
|
|
186
|
+
The benchmark shows relative performance:
|
|
187
|
+
```
|
|
188
|
+
Comparison:
|
|
189
|
+
MinimumBehavioralCoverage (with violation): 2456.7 i/s
|
|
190
|
+
MinimumBehavioralCoverage (without violation): 1682.5 i/s - 1.46x slower
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
This is expected: finding and reporting violations takes slightly more time than simply traversing the AST.
|
|
194
|
+
|
|
195
|
+
### Memory Usage
|
|
196
|
+
|
|
197
|
+
Shows memory consumption in MB for processing large files:
|
|
198
|
+
```
|
|
199
|
+
RSpecGuide/MinimumBehavioralCoverage: 2.45 MB
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
This is the additional memory allocated during cop execution. Lower is better.
|
|
203
|
+
|
|
204
|
+
### Interpreting Results
|
|
205
|
+
|
|
206
|
+
**Excellent performance (⚡):**
|
|
207
|
+
- Typical files: > 2,000 i/s
|
|
208
|
+
- Large files: > 150 i/s
|
|
209
|
+
- No action needed
|
|
210
|
+
|
|
211
|
+
**Good performance (✅):**
|
|
212
|
+
- Typical files: 1,000-2,000 i/s
|
|
213
|
+
- Large files: 50-150 i/s
|
|
214
|
+
- Monitor, but acceptable
|
|
215
|
+
|
|
216
|
+
**Warning (⚠️):**
|
|
217
|
+
- Typical files: 500-1,000 i/s
|
|
218
|
+
- Large files: 25-50 i/s
|
|
219
|
+
- Consider optimization
|
|
220
|
+
|
|
221
|
+
**Critical (❌):**
|
|
222
|
+
- Typical files: < 500 i/s
|
|
223
|
+
- Large files: < 25 i/s
|
|
224
|
+
- Requires immediate optimization
|
|
225
|
+
|
|
226
|
+
## Optimization Tips
|
|
227
|
+
|
|
228
|
+
If a cop is performing poorly:
|
|
229
|
+
|
|
230
|
+
1. **Check AST traversal**: Use `def_node_matcher` instead of manual traversal
|
|
231
|
+
```ruby
|
|
232
|
+
# Slow: manual traversal
|
|
233
|
+
def on_block(node)
|
|
234
|
+
node.children.each do |child|
|
|
235
|
+
# ...
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Fast: node matcher
|
|
240
|
+
def_node_matcher :context_node?, <<~PATTERN
|
|
241
|
+
(block (send nil? :context ...) ...)
|
|
242
|
+
PATTERN
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
2. **Avoid redundant checks**: Cache results when possible
|
|
246
|
+
```ruby
|
|
247
|
+
# Slow: recalculating
|
|
248
|
+
def check(node)
|
|
249
|
+
if expensive_operation(node)
|
|
250
|
+
# ...
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Fast: memoization
|
|
255
|
+
def check(node)
|
|
256
|
+
@cache ||= {}
|
|
257
|
+
@cache[node] ||= expensive_operation(node)
|
|
258
|
+
end
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
3. **Limit scope**: Only traverse relevant parts of the AST
|
|
262
|
+
```ruby
|
|
263
|
+
# Slow: checking everything
|
|
264
|
+
def on_send(node)
|
|
265
|
+
check_all_sends(node)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Fast: early return
|
|
269
|
+
def on_send(node)
|
|
270
|
+
return unless relevant_method?(node)
|
|
271
|
+
check_specific_send(node)
|
|
272
|
+
end
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
4. **Use early returns**: Exit as soon as violation is found
|
|
276
|
+
```ruby
|
|
277
|
+
# Slow: checking everything
|
|
278
|
+
def check_all_contexts(contexts)
|
|
279
|
+
violations = []
|
|
280
|
+
contexts.each { |ctx| violations << check(ctx) }
|
|
281
|
+
violations
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Fast: early return
|
|
285
|
+
def check_all_contexts(contexts)
|
|
286
|
+
contexts.each { |ctx| return ctx if violation?(ctx) }
|
|
287
|
+
nil
|
|
288
|
+
end
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Continuous Monitoring
|
|
292
|
+
|
|
293
|
+
### Before Making Changes
|
|
294
|
+
|
|
295
|
+
Run benchmarks and save baseline:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
bundle exec ruby benchmark/cops_benchmark.rb > before.txt
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### After Making Changes
|
|
302
|
+
|
|
303
|
+
Run benchmarks again and compare:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
bundle exec ruby benchmark/cops_benchmark.rb > after.txt
|
|
307
|
+
diff before.txt after.txt
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Regression Testing
|
|
311
|
+
|
|
312
|
+
Compare with official baseline:
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
bundle exec ruby benchmark/cops_benchmark.rb > current.txt
|
|
316
|
+
diff benchmark/baseline_v0.3.1.txt current.txt
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Significant deviations (> 20% slower) should be investigated.
|
|
320
|
+
|
|
321
|
+
## CI/CD Integration
|
|
322
|
+
|
|
323
|
+
Add to your CI pipeline to catch performance regressions:
|
|
324
|
+
|
|
325
|
+
```yaml
|
|
326
|
+
# .github/workflows/ci.yml
|
|
327
|
+
- name: Run performance benchmarks
|
|
328
|
+
run: |
|
|
329
|
+
rake benchmark:quick
|
|
330
|
+
# Fail if any cop is critically slow
|
|
331
|
+
# (implementation depends on your CI setup)
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Baseline Files
|
|
335
|
+
|
|
336
|
+
Baseline files capture performance at specific versions:
|
|
337
|
+
|
|
338
|
+
- `baseline_v0.3.1.txt` - Current baseline (Ruby 3.4.7, 2025-10-30)
|
|
339
|
+
|
|
340
|
+
These files are tracked in git to enable historical comparison.
|
|
341
|
+
|
|
342
|
+
## Contributing
|
|
343
|
+
|
|
344
|
+
When adding new cops:
|
|
345
|
+
|
|
346
|
+
1. Run benchmarks for the new cop
|
|
347
|
+
2. Ensure performance meets baseline (> 1,000 i/s for typical files)
|
|
348
|
+
3. Document any performance considerations
|
|
349
|
+
4. Update baseline file if adding significant new functionality
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
================================================================================
|
|
2
|
+
RuboCop RSpec Guide - Performance Baseline v0.3.1
|
|
3
|
+
================================================================================
|
|
4
|
+
|
|
5
|
+
Date: 2025-10-30
|
|
6
|
+
Ruby Version: 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
|
|
7
|
+
RuboCop Version: 1.80.2
|
|
8
|
+
Gem Version: 0.3.1
|
|
9
|
+
Mode: QUICK (time: 2s, warmup: 1s)
|
|
10
|
+
|
|
11
|
+
================================================================================
|
|
12
|
+
TYPICAL FILES (10-20 lines, simple RSpec examples)
|
|
13
|
+
================================================================================
|
|
14
|
+
|
|
15
|
+
MinimumBehavioralCoverage (with violation): 2.457k (± 1.7%) i/s (407.05 μs/i)
|
|
16
|
+
MinimumBehavioralCoverage (without violation): 1.683k (± 1.4%) i/s (594.34 μs/i)
|
|
17
|
+
Comparison: with violation is 1.46x faster
|
|
18
|
+
|
|
19
|
+
HappyPathFirst: 2.003k (± 1.1%) i/s (499.23 μs/i)
|
|
20
|
+
|
|
21
|
+
ContextSetup: 2.040k (± 0.8%) i/s (490.26 μs/i)
|
|
22
|
+
|
|
23
|
+
DuplicateLetValues: 1.499k (± 1.0%) i/s (666.97 μs/i)
|
|
24
|
+
|
|
25
|
+
DuplicateBeforeHooks: 1.626k (± 1.6%) i/s (614.99 μs/i)
|
|
26
|
+
|
|
27
|
+
InvariantExamples: 1.504k (± 0.8%) i/s (664.90 μs/i)
|
|
28
|
+
|
|
29
|
+
DynamicAttributeEvaluation: 2.423k (± 0.7%) i/s (412.77 μs/i)
|
|
30
|
+
|
|
31
|
+
================================================================================
|
|
32
|
+
SCALABILITY (increasing file sizes)
|
|
33
|
+
================================================================================
|
|
34
|
+
|
|
35
|
+
MinimumBehavioralCoverage - 5 contexts (87 lines):
|
|
36
|
+
356.636 (± 0.3%) i/s (2.80 ms/i)
|
|
37
|
+
|
|
38
|
+
MinimumBehavioralCoverage - 10 contexts (172 lines):
|
|
39
|
+
166.626 (± 1.2%) i/s (6.00 ms/i)
|
|
40
|
+
|
|
41
|
+
MinimumBehavioralCoverage - 20 contexts (342 lines):
|
|
42
|
+
73.298 (± 1.4%) i/s (13.64 ms/i)
|
|
43
|
+
|
|
44
|
+
Scaling Analysis:
|
|
45
|
+
5 → 10 contexts: 2.1x slower (expected ~2x) ✅
|
|
46
|
+
10 → 20 contexts: 2.3x slower (expected ~2x) ✅
|
|
47
|
+
Overall complexity: O(n) - linear ✅
|
|
48
|
+
|
|
49
|
+
================================================================================
|
|
50
|
+
SUMMARY
|
|
51
|
+
================================================================================
|
|
52
|
+
|
|
53
|
+
Performance Rating: EXCELLENT ⚡
|
|
54
|
+
|
|
55
|
+
All cops meet or exceed performance targets:
|
|
56
|
+
- Typical files: 1,499-2,457 i/s (target: > 1,000 i/s) ✅
|
|
57
|
+
- Large files: 73-357 i/s (target: > 50 i/s) ✅
|
|
58
|
+
- Complexity: O(n) linear (target: no exponential) ✅
|
|
59
|
+
|
|
60
|
+
Real-world impact:
|
|
61
|
+
- 100 typical files: ~0.05 seconds
|
|
62
|
+
- 100 large files: ~1.4 seconds
|
|
63
|
+
- Suitable for CI/CD pipelines ✅
|
|
64
|
+
|
|
65
|
+
No optimization required at this time.
|
|
66
|
+
|
|
67
|
+
================================================================================
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
================================================================================
|
|
2
|
+
RuboCop RSpec Guide - Performance Baseline v0.4.0
|
|
3
|
+
================================================================================
|
|
4
|
+
|
|
5
|
+
Date: 2025-10-30
|
|
6
|
+
Ruby Version: 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
|
|
7
|
+
RuboCop Version: 1.80.2
|
|
8
|
+
Gem Version: 0.4.0
|
|
9
|
+
Mode: QUICK (time: 2s, warmup: 1s)
|
|
10
|
+
|
|
11
|
+
Changes in v0.4.0:
|
|
12
|
+
- Integration with RuboCop::Cop::RSpec::Base for all RSpec cops
|
|
13
|
+
- Native support for let_it_be and let_it_be! from rspec-rails
|
|
14
|
+
- Performance optimizations: fast pre-checks + local matchers for hot paths
|
|
15
|
+
- InvariantExamples: 4.25x faster than v0.3.1 baseline
|
|
16
|
+
|
|
17
|
+
================================================================================
|
|
18
|
+
TYPICAL FILES (10-20 lines, simple RSpec examples)
|
|
19
|
+
================================================================================
|
|
20
|
+
|
|
21
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/bundler/rubygems_ext.rb:64: warning: already initialized constant Gem::Platform::JAVA
|
|
22
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/rubygems/platform.rb:259: warning: previous definition of JAVA was here
|
|
23
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/bundler/rubygems_ext.rb:65: warning: already initialized constant Gem::Platform::MSWIN
|
|
24
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/rubygems/platform.rb:260: warning: previous definition of MSWIN was here
|
|
25
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/bundler/rubygems_ext.rb:66: warning: already initialized constant Gem::Platform::MSWIN64
|
|
26
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/rubygems/platform.rb:261: warning: previous definition of MSWIN64 was here
|
|
27
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/bundler/rubygems_ext.rb:67: warning: already initialized constant Gem::Platform::MINGW
|
|
28
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/rubygems/platform.rb:262: warning: previous definition of MINGW was here
|
|
29
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/bundler/rubygems_ext.rb:68: warning: already initialized constant Gem::Platform::X64_MINGW
|
|
30
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/rubygems/platform.rb:264: warning: previous definition of X64_MINGW was here
|
|
31
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/bundler/rubygems_ext.rb:70: warning: already initialized constant Gem::Platform::UNIVERSAL_MINGW
|
|
32
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/rubygems/platform.rb:265: warning: previous definition of UNIVERSAL_MINGW was here
|
|
33
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/bundler/rubygems_ext.rb:71: warning: already initialized constant Gem::Platform::WINDOWS
|
|
34
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/rubygems/platform.rb:266: warning: previous definition of WINDOWS was here
|
|
35
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/bundler/rubygems_ext.rb:72: warning: already initialized constant Gem::Platform::X64_LINUX
|
|
36
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/rubygems/platform.rb:267: warning: previous definition of X64_LINUX was here
|
|
37
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/bundler/rubygems_ext.rb:73: warning: already initialized constant Gem::Platform::X64_LINUX_MUSL
|
|
38
|
+
/nix/store/lgf2l2wkr5845485qw254skgc0bdvbnc-ruby-3.4.7/lib/ruby/3.4.0/rubygems/platform.rb:268: warning: previous definition of X64_LINUX_MUSL was here
|
|
39
|
+
================================================================================
|
|
40
|
+
RuboCop RSpec Guide - Cops Performance Benchmark
|
|
41
|
+
================================================================================
|
|
42
|
+
|
|
43
|
+
Mode: QUICK (fast feedback, ~1 minute)
|
|
44
|
+
Tip: Use FULL_BENCHMARK=1 for more accurate measurements
|
|
45
|
+
|
|
46
|
+
ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
|
|
47
|
+
Warming up --------------------------------------
|
|
48
|
+
MinimumBehavioralCoverage (with violation)
|
|
49
|
+
220.000 i/100ms
|
|
50
|
+
MinimumBehavioralCoverage (without violation)
|
|
51
|
+
168.000 i/100ms
|
|
52
|
+
Calculating -------------------------------------
|
|
53
|
+
MinimumBehavioralCoverage (with violation)
|
|
54
|
+
2.167k (± 2.0%) i/s (461.40 μs/i) - 4.400k in 2.030998s
|
|
55
|
+
MinimumBehavioralCoverage (without violation)
|
|
56
|
+
1.649k (± 1.9%) i/s (606.49 μs/i) - 3.360k in 2.038552s
|
|
57
|
+
|
|
58
|
+
Comparison:
|
|
59
|
+
MinimumBehavioralCoverage (with violation): 2167.3 i/s
|
|
60
|
+
MinimumBehavioralCoverage (without violation): 1648.8 i/s - 1.31x slower
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
|
|
64
|
+
Warming up --------------------------------------
|
|
65
|
+
HappyPathFirst (with violation)
|
|
66
|
+
152.000 i/100ms
|
|
67
|
+
HappyPathFirst (without violation)
|
|
68
|
+
159.000 i/100ms
|
|
69
|
+
Calculating -------------------------------------
|
|
70
|
+
HappyPathFirst (with violation)
|
|
71
|
+
1.507k (± 1.7%) i/s (663.61 μs/i) - 3.040k in 2.017941s
|
|
72
|
+
HappyPathFirst (without violation)
|
|
73
|
+
1.643k (± 1.0%) i/s (608.51 μs/i) - 3.339k in 2.032019s
|
|
74
|
+
|
|
75
|
+
Comparison:
|
|
76
|
+
HappyPathFirst (without violation): 1643.4 i/s
|
|
77
|
+
HappyPathFirst (with violation): 1506.9 i/s - 1.09x slower
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
|
|
81
|
+
Warming up --------------------------------------
|
|
82
|
+
ContextSetup (with violation)
|
|
83
|
+
177.000 i/100ms
|
|
84
|
+
ContextSetup (without violation)
|
|
85
|
+
187.000 i/100ms
|
|
86
|
+
Calculating -------------------------------------
|
|
87
|
+
ContextSetup (with violation)
|
|
88
|
+
1.775k (± 1.5%) i/s (563.40 μs/i) - 3.717k in 2.094659s
|
|
89
|
+
ContextSetup (without violation)
|
|
90
|
+
1.894k (± 1.3%) i/s (527.88 μs/i) - 3.927k in 2.073325s
|
|
91
|
+
|
|
92
|
+
Comparison:
|
|
93
|
+
ContextSetup (without violation): 1894.4 i/s
|
|
94
|
+
ContextSetup (with violation): 1774.9 i/s - 1.07x slower
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
|
|
98
|
+
Warming up --------------------------------------
|
|
99
|
+
DuplicateLetValues (with violation)
|
|
100
|
+
120.000 i/100ms
|
|
101
|
+
DuplicateLetValues (without violation)
|
|
102
|
+
115.000 i/100ms
|
|
103
|
+
Calculating -------------------------------------
|
|
104
|
+
DuplicateLetValues (with violation)
|
|
105
|
+
1.199k (± 1.3%) i/s (834.28 μs/i) - 2.400k in 2.002646s
|
|
106
|
+
DuplicateLetValues (without violation)
|
|
107
|
+
1.177k (± 1.6%) i/s (849.34 μs/i) - 2.415k in 2.051681s
|
|
108
|
+
|
|
109
|
+
Comparison:
|
|
110
|
+
DuplicateLetValues (with violation): 1198.6 i/s
|
|
111
|
+
DuplicateLetValues (without violation): 1177.4 i/s - same-ish: difference falls within error
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
|
|
115
|
+
Warming up --------------------------------------
|
|
116
|
+
DuplicateBeforeHooks (with violation)
|
|
117
|
+
125.000 i/100ms
|
|
118
|
+
DuplicateBeforeHooks (without violation)
|
|
119
|
+
151.000 i/100ms
|
|
120
|
+
Calculating -------------------------------------
|
|
121
|
+
DuplicateBeforeHooks (with violation)
|
|
122
|
+
1.269k (± 1.2%) i/s (787.73 μs/i) - 2.625k in 2.068076s
|
|
123
|
+
DuplicateBeforeHooks (without violation)
|
|
124
|
+
1.531k (± 2.2%) i/s (653.27 μs/i) - 3.171k in 2.072556s
|
|
125
|
+
|
|
126
|
+
Comparison:
|
|
127
|
+
DuplicateBeforeHooks (without violation): 1530.8 i/s
|
|
128
|
+
DuplicateBeforeHooks (with violation): 1269.5 i/s - 1.21x slower
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
|
|
132
|
+
Warming up --------------------------------------
|
|
133
|
+
InvariantExamples (with violation)
|
|
134
|
+
88.000 i/100ms
|
|
135
|
+
InvariantExamples (without violation)
|
|
136
|
+
83.000 i/100ms
|
|
137
|
+
Calculating -------------------------------------
|
|
138
|
+
InvariantExamples (with violation)
|
|
139
|
+
881.991 (± 1.1%) i/s (1.13 ms/i) - 1.848k in 2.095538s
|
|
140
|
+
InvariantExamples (without violation)
|
|
141
|
+
858.071 (± 1.6%) i/s (1.17 ms/i) - 1.743k in 2.031829s
|
|
142
|
+
|
|
143
|
+
Comparison:
|
|
144
|
+
InvariantExamples (with violation): 882.0 i/s
|
|
145
|
+
InvariantExamples (without violation): 858.1 i/s - same-ish: difference falls within error
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
|
|
149
|
+
Warming up --------------------------------------
|
|
150
|
+
DynamicAttributeEvaluation (with violation)
|
|
151
|
+
250.000 i/100ms
|
|
152
|
+
DynamicAttributeEvaluation (without violation)
|
|
153
|
+
219.000 i/100ms
|
|
154
|
+
Calculating -------------------------------------
|
|
155
|
+
DynamicAttributeEvaluation (with violation)
|
|
156
|
+
2.467k (± 1.0%) i/s (405.35 μs/i) - 5.000k in 2.026967s
|
|
157
|
+
DynamicAttributeEvaluation (without violation)
|
|
158
|
+
2.194k (± 1.1%) i/s (455.71 μs/i) - 4.599k in 2.096088s
|
|
159
|
+
|
|
160
|
+
Comparison:
|
|
161
|
+
DynamicAttributeEvaluation (with violation): 2467.0 i/s
|
|
162
|
+
DynamicAttributeEvaluation (without violation): 2194.4 i/s - 1.12x slower
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
================================================================================
|
|
166
|
+
Benchmark completed!
|
|
167
|
+
================================================================================
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "benchmark/ips"
|
|
4
|
+
require "rubocop"
|
|
5
|
+
require_relative "../lib/rubocop-rspec-guide"
|
|
6
|
+
|
|
7
|
+
# Helper module for running benchmarks on cops
|
|
8
|
+
module BenchmarkHelper
|
|
9
|
+
# Run a cop against the given source code
|
|
10
|
+
#
|
|
11
|
+
# @param cop_class [Class] The cop class to benchmark
|
|
12
|
+
# @param source [String] The source code to analyze
|
|
13
|
+
# @return [Array<RuboCop::Cop::Offense>] The offenses found
|
|
14
|
+
def self.run_cop(cop_class, source)
|
|
15
|
+
cop = cop_class.new
|
|
16
|
+
processed_source = parse_source(source)
|
|
17
|
+
commissioner = RuboCop::Cop::Commissioner.new([cop])
|
|
18
|
+
commissioner.investigate(processed_source)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Parse source code into a ProcessedSource
|
|
22
|
+
#
|
|
23
|
+
# @param source [String] The source code to parse
|
|
24
|
+
# @return [RuboCop::ProcessedSource] The parsed source
|
|
25
|
+
def self.parse_source(source)
|
|
26
|
+
RuboCop::ProcessedSource.new(
|
|
27
|
+
source,
|
|
28
|
+
ruby_version,
|
|
29
|
+
nil
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Get the current Ruby version
|
|
34
|
+
#
|
|
35
|
+
# @return [Float] The Ruby version
|
|
36
|
+
def self.ruby_version
|
|
37
|
+
RUBY_VERSION.to_f
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Generate sample RSpec code with the given number of examples
|
|
41
|
+
#
|
|
42
|
+
# @param examples_count [Integer] Number of examples to generate
|
|
43
|
+
# @return [String] Generated RSpec code
|
|
44
|
+
def self.generate_rspec_code(examples_count: 10)
|
|
45
|
+
examples = (1..examples_count).map do |i|
|
|
46
|
+
<<~RUBY
|
|
47
|
+
it "does something #{i}" do
|
|
48
|
+
expect(subject.call).to eq(#{i})
|
|
49
|
+
end
|
|
50
|
+
RUBY
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
<<~RUBY
|
|
54
|
+
RSpec.describe MyClass do
|
|
55
|
+
subject { MyClass.new }
|
|
56
|
+
|
|
57
|
+
#{examples.join("\n")}
|
|
58
|
+
end
|
|
59
|
+
RUBY
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Generate sample RSpec code with contexts
|
|
63
|
+
#
|
|
64
|
+
# @param contexts_count [Integer] Number of contexts to generate
|
|
65
|
+
# @param examples_per_context [Integer] Number of examples per context
|
|
66
|
+
# @return [String] Generated RSpec code
|
|
67
|
+
def self.generate_context_code(contexts_count: 5, examples_per_context: 3)
|
|
68
|
+
contexts = (1..contexts_count).map do |i|
|
|
69
|
+
examples = (1..examples_per_context).map do |j|
|
|
70
|
+
<<~RUBY
|
|
71
|
+
it "does something #{j}" do
|
|
72
|
+
expect(result).to eq(#{j})
|
|
73
|
+
end
|
|
74
|
+
RUBY
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
<<~RUBY
|
|
78
|
+
context "when condition #{i}" do
|
|
79
|
+
let(:value) { #{i} }
|
|
80
|
+
|
|
81
|
+
#{examples.join("\n")}
|
|
82
|
+
end
|
|
83
|
+
RUBY
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
<<~RUBY
|
|
87
|
+
RSpec.describe MyClass do
|
|
88
|
+
#{contexts.join("\n")}
|
|
89
|
+
end
|
|
90
|
+
RUBY
|
|
91
|
+
end
|
|
92
|
+
end
|