fast_cov 0.1.4 → 0.1.5
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 +33 -5
- data/lib/fast_cov/configuration.rb +49 -5
- data/lib/fast_cov/singleton.rb +44 -0
- data/lib/fast_cov/trackers/abstract_tracker.rb +15 -4
- data/lib/fast_cov/trackers/const_get_tracker.rb +35 -0
- data/lib/fast_cov/trackers/factory_bot_tracker.rb +2 -3
- data/lib/fast_cov/trackers/file_tracker.rb +2 -21
- data/lib/fast_cov/version.rb +1 -1
- data/lib/fast_cov.rb +10 -56
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3ce0e302b02e7989034b39bd84cc69e7563378edb3ec31d10491899a4da74bac
|
|
4
|
+
data.tar.gz: 7273c905846579200383b0409d91ae8ea8066102e1c91d50af5a4c601e03f06e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 149d759f1b8005b69741319655c2366b8ff3f0422b8c978418942e50f67a0b81b6077e7810c0a70aad56bf80fe130f2fc7b4003f55dc28ac0780d3ce2b847dcb
|
|
7
|
+
data.tar.gz: 6b8a1e08762f8fdcc4d2488f9c49348c38f5701499fd96550fdb4362cf64089ca76e55ecbfb6d50523cb190e15ed588b4162e873b3419b238d3a1680a6f97016
|
data/README.md
CHANGED
|
@@ -51,8 +51,8 @@ Call `FastCov.configure` before using `start`/`stop`. The block yields a `Config
|
|
|
51
51
|
|
|
52
52
|
```ruby
|
|
53
53
|
FastCov.configure do |config|
|
|
54
|
-
config.root = Rails.root
|
|
55
|
-
config.ignored_path = Rails.root.join("vendor")
|
|
54
|
+
config.root = Rails.root
|
|
55
|
+
config.ignored_path = Rails.root.join("vendor")
|
|
56
56
|
config.threads = true
|
|
57
57
|
|
|
58
58
|
config.use FastCov::CoverageTracker
|
|
@@ -94,7 +94,7 @@ FastCov.reset # Clear configuration and trackers.
|
|
|
94
94
|
```ruby
|
|
95
95
|
# spec/support/fast_cov.rb
|
|
96
96
|
FastCov.configure do |config|
|
|
97
|
-
config.root = Rails.root
|
|
97
|
+
config.root = Rails.root
|
|
98
98
|
config.use FastCov::CoverageTracker
|
|
99
99
|
config.use FastCov::FileTracker
|
|
100
100
|
end
|
|
@@ -133,7 +133,7 @@ config.use FastCov::CoverageTracker
|
|
|
133
133
|
|
|
134
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
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
|
|
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
137
|
|
|
138
138
|
#### Disabling expensive features
|
|
139
139
|
|
|
@@ -189,6 +189,34 @@ config.use FastCov::FactoryBotTracker
|
|
|
189
189
|
|
|
190
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
191
|
|
|
192
|
+
### ConstGetTracker
|
|
193
|
+
|
|
194
|
+
Tracks constants looked up dynamically via `Module#const_get`. This catches dynamic constant lookups that static analysis (Prism) would miss.
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
config.use FastCov::ConstGetTracker
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### What it catches
|
|
201
|
+
|
|
202
|
+
- `Object.const_get("Foo::Bar")`
|
|
203
|
+
- Rails' `"UserMailer".constantize` (uses `const_get` under the hood)
|
|
204
|
+
- Any metaprogramming that looks up constants by string name
|
|
205
|
+
|
|
206
|
+
**Note:** This does NOT catch direct constant references like `Foo::Bar` in source code -- those compile to `opt_getconstant_path` bytecode and bypass `const_get`. Use `CoverageTracker` with `constant_references: true` for static analysis of literal constant references.
|
|
207
|
+
|
|
208
|
+
#### Options
|
|
209
|
+
|
|
210
|
+
| Option | Type | Default | Description |
|
|
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. |
|
|
215
|
+
|
|
216
|
+
#### How it works
|
|
217
|
+
|
|
218
|
+
Prepends a module on `Module` to intercept `const_get` calls. When a constant is looked up, the tracker calls `const_source_location` to find where the constant was defined and records that file.
|
|
219
|
+
|
|
192
220
|
## Writing custom trackers
|
|
193
221
|
|
|
194
222
|
There are two approaches to writing custom trackers: from scratch (minimal interface) or inheriting from `AbstractTracker` (batteries included).
|
|
@@ -295,7 +323,7 @@ Results from all trackers are merged, with later trackers overwriting earlier on
|
|
|
295
323
|
|
|
296
324
|
## Cache
|
|
297
325
|
|
|
298
|
-
FastCov caches constant reference resolution results in memory so files only need parsing once per process. The cache is process-level,
|
|
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`.
|
|
299
327
|
|
|
300
328
|
```ruby
|
|
301
329
|
FastCov::Cache.data # the raw cache hash
|
|
@@ -1,27 +1,71 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
3
5
|
module FastCov
|
|
4
6
|
class Configuration
|
|
5
|
-
|
|
7
|
+
class ConfigurationError < StandardError; end
|
|
8
|
+
|
|
9
|
+
attr_accessor :threads
|
|
10
|
+
attr_reader :root, :ignored_path
|
|
6
11
|
|
|
7
12
|
def initialize
|
|
8
13
|
@root = Dir.pwd
|
|
9
14
|
@ignored_path = nil
|
|
10
15
|
@threads = true
|
|
11
|
-
@
|
|
16
|
+
@tracker_definitions = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def root=(value)
|
|
20
|
+
return @root = nil if value.nil?
|
|
21
|
+
|
|
22
|
+
path = value.to_s
|
|
23
|
+
unless absolute_path?(path)
|
|
24
|
+
raise ConfigurationError, "root must be an absolute path, got: #{path.inspect}"
|
|
25
|
+
end
|
|
26
|
+
@root = path
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def ignored_path=(value)
|
|
30
|
+
return @ignored_path = nil if value.nil?
|
|
31
|
+
|
|
32
|
+
path = value.to_s
|
|
33
|
+
|
|
34
|
+
# Expand relative paths against root
|
|
35
|
+
unless absolute_path?(path)
|
|
36
|
+
path = File.join(@root, path)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Validate ignored_path is inside root
|
|
40
|
+
unless path.start_with?(@root)
|
|
41
|
+
raise ConfigurationError,
|
|
42
|
+
"ignored_path must be inside root (#{@root.inspect}), got: #{path.inspect}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
@ignored_path = path
|
|
12
46
|
end
|
|
13
47
|
|
|
14
48
|
def use(tracker_class, **options)
|
|
15
|
-
@
|
|
49
|
+
@tracker_definitions << {klass: tracker_class, options: options}
|
|
16
50
|
end
|
|
17
51
|
|
|
18
|
-
def
|
|
19
|
-
@
|
|
52
|
+
def install_trackers
|
|
53
|
+
@tracker_definitions.map do |entry|
|
|
54
|
+
tracker = entry[:klass].new(self, **entry[:options])
|
|
55
|
+
tracker.install if tracker.respond_to?(:install)
|
|
56
|
+
tracker
|
|
57
|
+
end
|
|
20
58
|
end
|
|
21
59
|
|
|
22
60
|
def reset
|
|
23
61
|
initialize
|
|
24
62
|
self
|
|
25
63
|
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def absolute_path?(path)
|
|
68
|
+
Pathname.new(path).absolute?
|
|
69
|
+
end
|
|
26
70
|
end
|
|
27
71
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FastCov
|
|
4
|
+
module Singleton
|
|
5
|
+
def configured?
|
|
6
|
+
!@trackers.nil? && !@trackers.empty?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def configure
|
|
10
|
+
@configuration = Configuration.new
|
|
11
|
+
yield(@configuration)
|
|
12
|
+
@trackers = @configuration.install_trackers
|
|
13
|
+
self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def start(&block)
|
|
17
|
+
raise "FastCov.configure must be called before start" unless configured?
|
|
18
|
+
@trackers.each(&:start)
|
|
19
|
+
if block
|
|
20
|
+
result = nil
|
|
21
|
+
begin
|
|
22
|
+
yield
|
|
23
|
+
ensure
|
|
24
|
+
result = stop
|
|
25
|
+
end
|
|
26
|
+
result
|
|
27
|
+
else
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def stop
|
|
33
|
+
raise "FastCov.configure must be called before stop" unless configured?
|
|
34
|
+
result = Set.new
|
|
35
|
+
@trackers.each { |t| result.merge(t.stop) }
|
|
36
|
+
Utils.relativize_paths(result, @configuration.root)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def reset
|
|
40
|
+
@trackers = nil
|
|
41
|
+
@configuration = nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -43,8 +43,8 @@ module FastCov
|
|
|
43
43
|
|
|
44
44
|
def record(abs_path)
|
|
45
45
|
return if !@threads && Thread.current != @started_thread
|
|
46
|
-
return unless
|
|
47
|
-
return if @ignored_path &&
|
|
46
|
+
return unless Utils.path_within?(abs_path, @root)
|
|
47
|
+
return if @ignored_path && Utils.path_within?(abs_path, @ignored_path)
|
|
48
48
|
@files.add(abs_path) if on_record(abs_path)
|
|
49
49
|
end
|
|
50
50
|
|
|
@@ -61,8 +61,19 @@ module FastCov
|
|
|
61
61
|
class << self
|
|
62
62
|
attr_accessor :active
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
# Record a file path. Accepts a path directly or a block that returns the path.
|
|
65
|
+
# If block given, it's only executed when tracker is active (avoids expensive work).
|
|
66
|
+
# Nil values are ignored.
|
|
67
|
+
#
|
|
68
|
+
# record("/path/to/file.rb") # direct path
|
|
69
|
+
# record { expensive_lookup } # lazy evaluation
|
|
70
|
+
# record("/path") { fallback } # path takes precedence
|
|
71
|
+
#
|
|
72
|
+
def record(abs_path = nil)
|
|
73
|
+
return unless active
|
|
74
|
+
|
|
75
|
+
path = abs_path || (yield if block_given?)
|
|
76
|
+
active.record(path) if path
|
|
66
77
|
end
|
|
67
78
|
|
|
68
79
|
def reset
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "abstract_tracker"
|
|
4
|
+
|
|
5
|
+
module FastCov
|
|
6
|
+
# Tracks constants looked up dynamically via Module#const_get.
|
|
7
|
+
#
|
|
8
|
+
# This catches dynamic constant lookups that static analysis misses:
|
|
9
|
+
# - Object.const_get("Foo::Bar")
|
|
10
|
+
# - Rails' "UserMailer".constantize (uses const_get under the hood)
|
|
11
|
+
# - Any metaprogramming that looks up constants by string name
|
|
12
|
+
#
|
|
13
|
+
# Note: This does NOT catch direct constant references like `Foo::Bar` in source
|
|
14
|
+
# code - those compile to opt_getconstant_path bytecode and bypass const_get.
|
|
15
|
+
# Use CoverageTracker with constant_references: true for static analysis.
|
|
16
|
+
#
|
|
17
|
+
# Register via: config.use FastCov::ConstGetTracker
|
|
18
|
+
# Options: root, ignored_path, threads (all default from config)
|
|
19
|
+
class ConstGetTracker < AbstractTracker
|
|
20
|
+
def install
|
|
21
|
+
Module.prepend(ConstGetPatch)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module ConstGetPatch
|
|
25
|
+
def const_get(name, inherit = true)
|
|
26
|
+
result = super
|
|
27
|
+
FastCov::ConstGetTracker.record do
|
|
28
|
+
location = self.const_source_location(name, inherit) rescue nil
|
|
29
|
+
location&.first
|
|
30
|
+
end
|
|
31
|
+
result
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -30,7 +30,7 @@ module FastCov
|
|
|
30
30
|
|
|
31
31
|
class << self
|
|
32
32
|
def record_factory_files(factory)
|
|
33
|
-
return unless
|
|
33
|
+
return unless active
|
|
34
34
|
|
|
35
35
|
definition = factory.definition
|
|
36
36
|
declarations = definition.instance_variable_get(:@declarations)
|
|
@@ -43,8 +43,7 @@ module FastCov
|
|
|
43
43
|
location = block.source_location
|
|
44
44
|
next unless location
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
@active.record(file_path)
|
|
46
|
+
record(location[0])
|
|
48
47
|
end
|
|
49
48
|
end
|
|
50
49
|
end
|
|
@@ -15,33 +15,14 @@ module FastCov
|
|
|
15
15
|
|
|
16
16
|
module FilePatch
|
|
17
17
|
def read(name, *args, **kwargs, &block)
|
|
18
|
-
FastCov::FileTracker.
|
|
19
|
-
super
|
|
18
|
+
super.tap { FastCov::FileTracker.record { File.expand_path(name) } }
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
def open(name, *args, **kwargs, &block)
|
|
23
22
|
mode = args[0]
|
|
24
23
|
is_read = mode.nil? || (mode.is_a?(String) && mode.start_with?("r")) ||
|
|
25
24
|
(mode.is_a?(Integer) && (mode & (File::WRONLY | File::RDWR)).zero?)
|
|
26
|
-
FastCov::FileTracker.
|
|
27
|
-
super
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
class << self
|
|
32
|
-
def record_for_active(path)
|
|
33
|
-
return unless @active
|
|
34
|
-
|
|
35
|
-
path_str = path.to_s
|
|
36
|
-
return if path_str.empty?
|
|
37
|
-
|
|
38
|
-
abs_path = begin
|
|
39
|
-
File.expand_path(path_str)
|
|
40
|
-
rescue ArgumentError, TypeError
|
|
41
|
-
return
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
@active.record(abs_path)
|
|
25
|
+
super.tap { FastCov::FileTracker.record { File.expand_path(name) } if is_read }
|
|
45
26
|
end
|
|
46
27
|
end
|
|
47
28
|
end
|
data/lib/fast_cov/version.rb
CHANGED
data/lib/fast_cov.rb
CHANGED
|
@@ -1,62 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "fast_cov/version"
|
|
4
3
|
require "fast_cov/fast_cov.#{RUBY_VERSION}"
|
|
5
|
-
require_relative "fast_cov/configuration"
|
|
6
|
-
require_relative "fast_cov/trackers/abstract_tracker"
|
|
7
|
-
require_relative "fast_cov/trackers/coverage_tracker"
|
|
8
|
-
require_relative "fast_cov/trackers/file_tracker"
|
|
9
|
-
require_relative "fast_cov/trackers/factory_bot_tracker"
|
|
10
4
|
|
|
11
5
|
module FastCov
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def start(&block)
|
|
25
|
-
raise "FastCov.configure must be called before start" unless configured?
|
|
26
|
-
@trackers.each(&:start)
|
|
27
|
-
if block
|
|
28
|
-
result = nil
|
|
29
|
-
begin
|
|
30
|
-
yield
|
|
31
|
-
ensure
|
|
32
|
-
result = stop
|
|
33
|
-
end
|
|
34
|
-
result
|
|
35
|
-
else
|
|
36
|
-
self
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def stop
|
|
41
|
-
raise "FastCov.configure must be called before stop" unless configured?
|
|
42
|
-
result = Set.new
|
|
43
|
-
@trackers.each { |t| result.merge(t.stop) }
|
|
44
|
-
Utils.relativize_paths(result, @configuration.root)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def reset
|
|
48
|
-
@trackers = nil
|
|
49
|
-
@configuration = nil
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
private
|
|
53
|
-
|
|
54
|
-
def install_trackers
|
|
55
|
-
@trackers = @configuration.trackers.map do |entry|
|
|
56
|
-
tracker = entry[:klass].new(@configuration, **entry[:options])
|
|
57
|
-
tracker.install if tracker.respond_to?(:install)
|
|
58
|
-
tracker
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
6
|
+
autoload :VERSION, "fast_cov/version"
|
|
7
|
+
autoload :Configuration, "fast_cov/configuration"
|
|
8
|
+
autoload :Singleton, "fast_cov/singleton"
|
|
9
|
+
autoload :AbstractTracker, "fast_cov/trackers/abstract_tracker"
|
|
10
|
+
autoload :CoverageTracker, "fast_cov/trackers/coverage_tracker"
|
|
11
|
+
autoload :FileTracker, "fast_cov/trackers/file_tracker"
|
|
12
|
+
autoload :FactoryBotTracker, "fast_cov/trackers/factory_bot_tracker"
|
|
13
|
+
autoload :ConstGetTracker, "fast_cov/trackers/const_get_tracker"
|
|
14
|
+
|
|
15
|
+
extend Singleton
|
|
62
16
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fast_cov
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ngan Pham
|
|
@@ -85,7 +85,9 @@ files:
|
|
|
85
85
|
- lib/fast_cov/configuration.rb
|
|
86
86
|
- lib/fast_cov/constant_extractor.rb
|
|
87
87
|
- lib/fast_cov/dev.rb
|
|
88
|
+
- lib/fast_cov/singleton.rb
|
|
88
89
|
- lib/fast_cov/trackers/abstract_tracker.rb
|
|
90
|
+
- lib/fast_cov/trackers/const_get_tracker.rb
|
|
89
91
|
- lib/fast_cov/trackers/coverage_tracker.rb
|
|
90
92
|
- lib/fast_cov/trackers/factory_bot_tracker.rb
|
|
91
93
|
- lib/fast_cov/trackers/file_tracker.rb
|