fast_cov 0.1.5 → 0.2.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/README.md +63 -228
- data/ext/fast_cov/fast_cov.c +85 -283
- data/ext/fast_cov/fast_cov.h +3 -14
- data/ext/fast_cov/fast_cov_utils.c +11 -53
- data/lib/fast_cov/benchmark/scenarios.rb +1 -33
- data/lib/fast_cov/connected_dependencies.rb +37 -0
- data/lib/fast_cov/coverage_map.rb +175 -0
- data/lib/fast_cov/trackers/abstract_tracker.rb +26 -24
- data/lib/fast_cov/trackers/const_get_tracker.rb +5 -7
- data/lib/fast_cov/trackers/factory_bot_tracker.rb +4 -5
- data/lib/fast_cov/trackers/file_tracker.rb +11 -4
- data/lib/fast_cov/version.rb +1 -1
- data/lib/fast_cov.rb +7 -10
- metadata +4 -6
- data/lib/fast_cov/configuration.rb +0 -71
- data/lib/fast_cov/constant_extractor.rb +0 -53
- data/lib/fast_cov/singleton.rb +0 -44
- data/lib/fast_cov/trackers/coverage_tracker.rb +0 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c1319e05ab0d7a740626e94ed880dacb09b980dfc0ededc6b1c0259383fb0667
|
|
4
|
+
data.tar.gz: dd7af260d917264ab2f30affbc3dd2dfa44f5ddba03bc8d1f8dd222e29a9817e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6b594da4510ba6bb43767030b4d25e3f7787e22923b6e76b48616f2a085c030d22c1d870976570f7ac74fe921fe2c1eabbeeb16471ec8a533588f9d5b9974fef
|
|
7
|
+
data.tar.gz: 98f1b25e2565a56af55e472096a4c3159110b27117b99682cd031888083ec68d5aef8788aeff7a2538af9a5287f588653f8717ee30160e1126f127a0316191ea
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# FastCov
|
|
2
2
|
|
|
3
|
-
A high-performance native C extension for tracking which Ruby source files are executed during test runs. Built for test impact analysis
|
|
3
|
+
A high-performance native C extension for tracking which Ruby source files are executed during test runs. Built for test impact analysis.
|
|
4
4
|
|
|
5
5
|
FastCov hooks directly into the Ruby VM's event system, avoiding the overhead of Ruby's built-in `Coverage` module. The result is file-level coverage tracking with minimal performance impact.
|
|
6
6
|
|
|
@@ -23,229 +23,128 @@ Then:
|
|
|
23
23
|
bundle install
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
The C extension compiles automatically during gem installation.
|
|
27
|
-
|
|
28
26
|
## Quick start
|
|
29
27
|
|
|
30
28
|
```ruby
|
|
31
29
|
require "fast_cov"
|
|
32
30
|
|
|
33
|
-
FastCov.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
config.use FastCov::FileTracker
|
|
37
|
-
end
|
|
31
|
+
coverage = FastCov::CoverageMap.new
|
|
32
|
+
coverage.root = File.expand_path("app")
|
|
33
|
+
coverage.use(FastCov::FileTracker)
|
|
38
34
|
|
|
39
|
-
result =
|
|
35
|
+
result = coverage.start do
|
|
40
36
|
# ... run a test ...
|
|
41
37
|
end
|
|
42
38
|
|
|
43
|
-
# => {
|
|
39
|
+
# => #<Set: {"models/user.rb", "config/settings.yml"}>
|
|
44
40
|
```
|
|
45
41
|
|
|
46
|
-
`stop` returns a
|
|
42
|
+
`CoverageMap#stop` returns a `Set` of paths relative to `root`.
|
|
47
43
|
|
|
48
|
-
##
|
|
44
|
+
## CoverageMap
|
|
49
45
|
|
|
50
|
-
|
|
46
|
+
`FastCov::CoverageMap` is the primary API.
|
|
51
47
|
|
|
52
48
|
```ruby
|
|
53
|
-
FastCov.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
coverage = FastCov::CoverageMap.new
|
|
50
|
+
coverage.root = Rails.root.to_s
|
|
51
|
+
coverage.threads = true
|
|
52
|
+
coverage.ignored_paths = Rails.root.join("vendor")
|
|
57
53
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
end
|
|
54
|
+
coverage.use(FastCov::FileTracker)
|
|
55
|
+
coverage.use(FastCov::FactoryBotTracker)
|
|
61
56
|
```
|
|
62
57
|
|
|
63
|
-
###
|
|
58
|
+
### Options
|
|
64
59
|
|
|
65
60
|
| Option | Type | Default | Description |
|
|
66
61
|
|---|---|---|---|
|
|
67
|
-
| `root` | String | `Dir.pwd` | Absolute
|
|
68
|
-
| `ignored_path` | String | `nil` | Path prefix to exclude (e.g., vendor/bundle). |
|
|
62
|
+
| `root` | String | `Dir.pwd` | Absolute project root. Returned paths are relativized to this root. |
|
|
69
63
|
| `threads` | Boolean | `true` | `true` tracks all threads. `false` tracks only the thread that called `start`. |
|
|
64
|
+
| `ignored_paths` | String, Pathname, or Array | `[]` | Paths under `root` to exclude from tracking. Single values are wrapped into an array, and relative entries are resolved against `root` when coverage starts. |
|
|
70
65
|
|
|
71
|
-
###
|
|
72
|
-
|
|
73
|
-
Trackers are registered with `config.use`. Each tracker receives the config object and any options you pass:
|
|
74
|
-
|
|
75
|
-
```ruby
|
|
76
|
-
config.use FastCov::CoverageTracker
|
|
77
|
-
config.use FastCov::CoverageTracker, constant_references: false
|
|
78
|
-
config.use FastCov::FileTracker, ignored_path: "/custom/ignore"
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Singleton API
|
|
66
|
+
### Lifecycle
|
|
82
67
|
|
|
83
68
|
```ruby
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
FastCov.start { ... } # Block form: start, yield, stop. Returns results.
|
|
88
|
-
FastCov.configured? # true after configure, false after reset.
|
|
89
|
-
FastCov.reset # Clear configuration and trackers.
|
|
69
|
+
coverage.start # starts tracking and returns the CoverageMap
|
|
70
|
+
coverage.stop # stops tracking and returns a Set
|
|
71
|
+
coverage.start { ... } # block form: start, yield, stop
|
|
90
72
|
```
|
|
91
73
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
```ruby
|
|
95
|
-
# spec/support/fast_cov.rb
|
|
96
|
-
FastCov.configure do |config|
|
|
97
|
-
config.root = Rails.root
|
|
98
|
-
config.use FastCov::CoverageTracker
|
|
99
|
-
config.use FastCov::FileTracker
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
RSpec.configure do |config|
|
|
103
|
-
config.around(:each) do |example|
|
|
104
|
-
result = FastCov.start { example.run }
|
|
105
|
-
# result is a hash of impacted file paths
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
```
|
|
74
|
+
Native line coverage is always enabled. Extra trackers registered with `use` are additive.
|
|
109
75
|
|
|
110
76
|
## Trackers
|
|
111
77
|
|
|
112
|
-
### CoverageTracker
|
|
113
|
-
|
|
114
|
-
Wraps the native C extension. Handles line event tracking, allocation tracing, and constant reference resolution.
|
|
115
|
-
|
|
116
|
-
```ruby
|
|
117
|
-
config.use FastCov::CoverageTracker
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
#### Options
|
|
121
|
-
|
|
122
|
-
| Option | Type | Default | Description |
|
|
123
|
-
|---|---|---|---|
|
|
124
|
-
| `root` | String | `config.root` | Override the root path for this tracker. |
|
|
125
|
-
| `ignored_path` | String | `config.ignored_path` | Override the ignored path for this tracker. |
|
|
126
|
-
| `threads` | Boolean | `config.threads` | Override the threading mode for this tracker. |
|
|
127
|
-
| `allocations` | Boolean | `true` | Track object allocations and resolve class hierarchies to source files. |
|
|
128
|
-
| `constant_references` | Boolean | `true` | Parse source with Prism for constant references and resolve them to defining files. |
|
|
129
|
-
|
|
130
|
-
#### What it tracks
|
|
131
|
-
|
|
132
|
-
**Line events** -- hooks `RUBY_EVENT_LINE` to record which files execute. Uses pointer caching (`rb_sourcefile()` returns stable pointers) to skip redundant file checks with a single integer comparison.
|
|
133
|
-
|
|
134
|
-
**Allocation tracing** (`allocations: true`) -- hooks `RUBY_INTERNAL_EVENT_NEWOBJ` to capture `T_OBJECT` and `T_STRUCT` allocations. At stop time, walks each instantiated class's ancestor chain and resolves every ancestor to its source file. This catches empty models, structs, and Data objects that line events alone would miss.
|
|
135
|
-
|
|
136
|
-
**Constant reference resolution** (`constant_references: true`) -- at stop time, parses tracked files with Prism and walks the AST for `ConstantPathNode` and `ConstantReadNode` to extract constant references, then resolves each constant to its defining file via `Object.const_source_location`. Resolution is transitive (up to 10 rounds) and cached by filename for the lifetime of the process.
|
|
137
|
-
|
|
138
|
-
#### Disabling expensive features
|
|
139
|
-
|
|
140
|
-
For maximum speed when you only need line-level file tracking:
|
|
141
|
-
|
|
142
|
-
```ruby
|
|
143
|
-
config.use FastCov::CoverageTracker, allocations: false, constant_references: false
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
This disables the NEWOBJ hook (no per-allocation overhead) and skips AST parsing at stop time.
|
|
147
|
-
|
|
148
78
|
### FileTracker
|
|
149
79
|
|
|
150
|
-
Tracks files read from disk during coverage
|
|
80
|
+
Tracks files read from disk during coverage, including JSON, YAML, ERB templates, and any file accessed via `File.read` or read-mode `File.open`.
|
|
151
81
|
|
|
152
82
|
```ruby
|
|
153
|
-
|
|
83
|
+
coverage.use(FastCov::FileTracker)
|
|
154
84
|
```
|
|
155
85
|
|
|
156
|
-
#### Options
|
|
157
|
-
|
|
158
|
-
| Option | Type | Default | Description |
|
|
159
|
-
|---|---|---|---|
|
|
160
|
-
| `root` | String | `config.root` | Override the root path for this tracker. |
|
|
161
|
-
| `ignored_path` | String | `config.ignored_path` | Override the ignored path for this tracker. |
|
|
162
|
-
| `threads` | Boolean | `config.threads` | Override the threading mode for this tracker. |
|
|
163
|
-
|
|
164
|
-
#### How it works
|
|
165
|
-
|
|
166
|
-
Prepends a module on `File.singleton_class` to intercept `File.read` and `File.open` (read-mode only). When a file within the root is read during coverage, its path is recorded. Write operations (`"w"`, `"a"`, etc.) are ignored.
|
|
167
|
-
|
|
168
|
-
This catches `YAML.load_file`, `JSON.parse(File.read(...))`, `CSV.read`, ERB template loading, and any other pattern that goes through `File.read` or `File.open`.
|
|
169
|
-
|
|
170
86
|
### FactoryBotTracker
|
|
171
87
|
|
|
172
|
-
Tracks FactoryBot factory definition files when factories are used
|
|
88
|
+
Tracks FactoryBot factory definition files when factories are used. This is useful because factory files are often loaded before coverage starts.
|
|
173
89
|
|
|
174
90
|
```ruby
|
|
175
|
-
|
|
91
|
+
coverage.use(FastCov::FactoryBotTracker)
|
|
176
92
|
```
|
|
177
93
|
|
|
178
|
-
**Requires:** The `factory_bot` gem must be installed. Raises `LoadError` if FactoryBot is not defined.
|
|
179
|
-
|
|
180
|
-
#### Options
|
|
181
|
-
|
|
182
|
-
| Option | Type | Default | Description |
|
|
183
|
-
|---|---|---|---|
|
|
184
|
-
| `root` | String | `config.root` | Override the root path for this tracker. |
|
|
185
|
-
| `ignored_path` | String | `config.ignored_path` | Override the ignored path for this tracker. |
|
|
186
|
-
| `threads` | Boolean | `config.threads` | Override the threading mode for this tracker. |
|
|
187
|
-
|
|
188
|
-
#### How it works
|
|
189
|
-
|
|
190
|
-
Prepends a module on `FactoryBot.factories.singleton_class` to intercept the `find` method (called by `create`, `build`, etc.). When a factory is used, the tracker walks its declaration blocks and extracts `source_location` from each proc to find the factory definition file.
|
|
191
|
-
|
|
192
94
|
### ConstGetTracker
|
|
193
95
|
|
|
194
|
-
Tracks constants looked up dynamically via `Module#const_get`.
|
|
96
|
+
Tracks constants looked up dynamically via `Module#const_get`.
|
|
195
97
|
|
|
196
98
|
```ruby
|
|
197
|
-
|
|
99
|
+
coverage.use(FastCov::ConstGetTracker)
|
|
198
100
|
```
|
|
199
101
|
|
|
200
|
-
|
|
102
|
+
This catches patterns such as:
|
|
201
103
|
|
|
202
104
|
- `Object.const_get("Foo::Bar")`
|
|
203
|
-
- Rails
|
|
204
|
-
-
|
|
105
|
+
- Rails `"UserMailer".constantize`
|
|
106
|
+
- metaprogramming that resolves constants from strings
|
|
205
107
|
|
|
206
|
-
|
|
108
|
+
It does not catch direct constant references such as `Foo::Bar` in source code.
|
|
207
109
|
|
|
208
|
-
|
|
110
|
+
## Low-level native coverage
|
|
209
111
|
|
|
210
|
-
|
|
211
|
-
|---|---|---|---|
|
|
212
|
-
| `root` | String | `config.root` | Override the root path for this tracker. |
|
|
213
|
-
| `ignored_path` | String | `config.ignored_path` | Override the ignored path for this tracker. |
|
|
214
|
-
| `threads` | Boolean | `config.threads` | Override the threading mode for this tracker. |
|
|
112
|
+
`FastCov::Coverage` is still available as a low-level primitive:
|
|
215
113
|
|
|
216
|
-
|
|
114
|
+
```ruby
|
|
115
|
+
cov = FastCov::Coverage.new(
|
|
116
|
+
root: "/repo/app",
|
|
117
|
+
ignored_paths: ["/repo/app/vendor"],
|
|
118
|
+
threads: true
|
|
119
|
+
)
|
|
120
|
+
```
|
|
217
121
|
|
|
218
|
-
|
|
122
|
+
This API is mainly useful for internal use and low-level tests. `CoverageMap` is the intended public orchestration API.
|
|
219
123
|
|
|
220
124
|
## Writing custom trackers
|
|
221
125
|
|
|
222
|
-
There are two approaches
|
|
126
|
+
There are two approaches: a minimal custom tracker, or inheriting from `AbstractTracker`.
|
|
223
127
|
|
|
224
128
|
### Option 1: From scratch
|
|
225
129
|
|
|
226
|
-
Any object that responds to `start` and `stop` can be
|
|
130
|
+
Any object that responds to `start` and `stop` can be used.
|
|
227
131
|
|
|
228
132
|
```ruby
|
|
229
133
|
class MyTracker
|
|
230
|
-
def initialize(
|
|
231
|
-
@
|
|
134
|
+
def initialize(coverage_map, **options)
|
|
135
|
+
@coverage_map = coverage_map
|
|
232
136
|
@options = options
|
|
233
|
-
@files =
|
|
137
|
+
@files = Set.new
|
|
234
138
|
end
|
|
235
139
|
|
|
236
140
|
def install
|
|
237
|
-
# Optional: one-time setup (called during configure)
|
|
238
|
-
# Good place to patch classes, set up hooks, etc.
|
|
239
141
|
end
|
|
240
142
|
|
|
241
143
|
def start
|
|
242
|
-
@files =
|
|
243
|
-
# Begin tracking
|
|
144
|
+
@files = Set.new
|
|
244
145
|
end
|
|
245
146
|
|
|
246
147
|
def stop
|
|
247
|
-
# Stop tracking and return results
|
|
248
|
-
# Paths should be absolute; FastCov will relativize them to config.root
|
|
249
148
|
@files
|
|
250
149
|
end
|
|
251
150
|
end
|
|
@@ -253,23 +152,21 @@ end
|
|
|
253
152
|
|
|
254
153
|
### Option 2: Inherit from AbstractTracker
|
|
255
154
|
|
|
256
|
-
`AbstractTracker` provides
|
|
155
|
+
`AbstractTracker` provides:
|
|
257
156
|
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
-
|
|
157
|
+
- path filtering through the owning `CoverageMap`
|
|
158
|
+
- thread-aware recording
|
|
159
|
+
- lifecycle management
|
|
160
|
+
- class-level `record` dispatch for patched hooks
|
|
261
161
|
|
|
262
162
|
```ruby
|
|
263
163
|
class MyTracker < FastCov::AbstractTracker
|
|
264
164
|
def install
|
|
265
|
-
# Patch the class/module you want to track
|
|
266
165
|
SomeClass.singleton_class.prepend(MyPatch)
|
|
267
166
|
end
|
|
268
167
|
|
|
269
168
|
module MyPatch
|
|
270
169
|
def some_method(...)
|
|
271
|
-
# Record the file when this method is called
|
|
272
|
-
# Uses inherited class method - no need to check .active
|
|
273
170
|
MyTracker.record(some_file_path)
|
|
274
171
|
super
|
|
275
172
|
end
|
|
@@ -277,91 +174,29 @@ class MyTracker < FastCov::AbstractTracker
|
|
|
277
174
|
end
|
|
278
175
|
```
|
|
279
176
|
|
|
280
|
-
####
|
|
281
|
-
|
|
282
|
-
Override these methods as needed:
|
|
283
|
-
|
|
284
|
-
| Method | When called | Purpose |
|
|
285
|
-
|---|---|---|
|
|
286
|
-
| `install` | Once during `configure` | Set up patches, hooks, instrumentation |
|
|
287
|
-
| `on_start` | At the beginning of `start` | Initialize tracker-specific state |
|
|
288
|
-
| `on_stop` | At the beginning of `stop` | Clean up tracker-specific state |
|
|
289
|
-
| `on_record(path)` | When `record(path)` is called | Return `true` to record, `false` to skip |
|
|
177
|
+
#### Hooks
|
|
290
178
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
def install
|
|
298
|
-
return unless defined?(ActiveSupport::Notifications)
|
|
299
|
-
|
|
300
|
-
ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
|
|
301
|
-
# Extract the caller location from the backtrace
|
|
302
|
-
caller_locations(1, 20).each do |loc|
|
|
303
|
-
path = loc.absolute_path
|
|
304
|
-
next unless path
|
|
305
|
-
|
|
306
|
-
# Uses inherited class method - safely no-ops if tracker isn't active
|
|
307
|
-
QueryTracker.record(path)
|
|
308
|
-
break
|
|
309
|
-
end
|
|
310
|
-
end
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
### Tracker lifecycle
|
|
316
|
-
|
|
317
|
-
1. `initialize(config, **options)` — Called when registered via `config.use`
|
|
318
|
-
2. `install` — Called once after all trackers are registered
|
|
319
|
-
3. `start` — Called on `FastCov.start` (in registration order)
|
|
320
|
-
4. `stop` — Called on `FastCov.stop` (in reverse order), must return `{ path => true }`
|
|
321
|
-
|
|
322
|
-
Results from all trackers are merged, with later trackers overwriting earlier ones for duplicate keys.
|
|
323
|
-
|
|
324
|
-
## Cache
|
|
325
|
-
|
|
326
|
-
FastCov caches constant reference resolution results in memory so files only need parsing once per process. The cache is process-level, keyed by filename, and populated automatically during `stop`.
|
|
327
|
-
|
|
328
|
-
```ruby
|
|
329
|
-
FastCov::Cache.data # the raw cache hash
|
|
330
|
-
FastCov::Cache.clear # empty the cache
|
|
331
|
-
FastCov::Cache.data = {} # replace cache contents
|
|
332
|
-
```
|
|
179
|
+
| Method | When called |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `install` | Once when the tracker is registered with `CoverageMap#use` |
|
|
182
|
+
| `on_start` | At the beginning of `start` |
|
|
183
|
+
| `on_stop` | At the beginning of `stop` |
|
|
184
|
+
| `on_record(path)` | Before a path is added to the result set |
|
|
333
185
|
|
|
334
186
|
## Local development with path: gems
|
|
335
187
|
|
|
336
188
|
When developing FastCov alongside a consuming project, use the compile entrypoint to auto-compile the C extension:
|
|
337
189
|
|
|
338
190
|
```ruby
|
|
339
|
-
# Gemfile
|
|
340
191
|
gem "fast_cov", path: "../fast_cov", require: "fast_cov/dev"
|
|
341
192
|
```
|
|
342
193
|
|
|
343
|
-
This compiles on first use and detects source changes for recompilation.
|
|
344
|
-
|
|
345
194
|
## Development
|
|
346
195
|
|
|
347
196
|
```sh
|
|
348
197
|
git clone <repo>
|
|
349
198
|
cd fast_cov
|
|
350
199
|
bundle install
|
|
351
|
-
bundle exec rake compile
|
|
352
|
-
bundle exec
|
|
200
|
+
bundle exec rake compile
|
|
201
|
+
bundle exec rspec --fail-fast
|
|
353
202
|
```
|
|
354
|
-
|
|
355
|
-
### Benchmarking
|
|
356
|
-
|
|
357
|
-
```sh
|
|
358
|
-
bin/benchmark --baseline # save current performance as baseline
|
|
359
|
-
# ... make changes ...
|
|
360
|
-
bin/benchmark # compare against baseline
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
Override iteration count: `ITERATIONS=5000 bin/benchmark`
|
|
364
|
-
|
|
365
|
-
## License
|
|
366
|
-
|
|
367
|
-
MIT
|