fast_cov 0.2.1 → 0.3.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 +43 -1
- data/lib/fast_cov/coverage_map.rb +16 -16
- data/lib/fast_cov/static_map/reference_extractor.rb +156 -0
- data/lib/fast_cov/static_map.rb +257 -0
- data/lib/fast_cov/version.rb +1 -1
- data/lib/fast_cov.rb +1 -0
- 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: 4a8e169598d457f54b52e872c65d55a9e87164bcac66654d7b21d9497aeba43a
|
|
4
|
+
data.tar.gz: 678150e27c7fb664bc5786764b063c1abb75218048f226b39495bc97c012ce75
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 68e671eab2e3583313b60e408f179006d77aa0d571d1b258b971916b38642fcedae05b8b728ff0588000bd69ada0183a3502b8f6b4e90a37fda3847be3dd6fcd
|
|
7
|
+
data.tar.gz: d4531b99a2ca2cfb1a566f63bed51444a91bedfd0356a33d50101f18aafa0020ed40d652199de538cb351c6ac2e5e6a7185c1a3aaef316bc444779767a19ad23
|
data/README.md
CHANGED
|
@@ -121,6 +121,48 @@ cov = FastCov::Coverage.new(
|
|
|
121
121
|
|
|
122
122
|
This API is mainly useful for internal use and low-level tests. `CoverageMap` is the intended public orchestration API.
|
|
123
123
|
|
|
124
|
+
## StaticMap
|
|
125
|
+
|
|
126
|
+
`FastCov::StaticMap` is a build-time API for static dependency mapping. It parses Ruby files with Prism, resolves literal constant references, and builds a direct dependency graph. Transitive closures are computed lazily on demand.
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
static_map = FastCov::StaticMap.new(root: Rails.root)
|
|
130
|
+
static_map.build("spec/**/*_spec.rb")
|
|
131
|
+
|
|
132
|
+
# Direct dependencies for a single file
|
|
133
|
+
static_map.dependencies("/app/spec/models/user_spec.rb")
|
|
134
|
+
# => ["/app/app/models/user.rb"]
|
|
135
|
+
|
|
136
|
+
# Transitive closure (computed and cached on first call)
|
|
137
|
+
static_map.transitive_dependencies("/app/spec/models/user_spec.rb")
|
|
138
|
+
# => ["/app/app/models/account.rb", "/app/app/models/user.rb"]
|
|
139
|
+
|
|
140
|
+
# Raw direct graph
|
|
141
|
+
static_map.direct_graph
|
|
142
|
+
# => { "/app/spec/models/user_spec.rb" => ["/app/app/models/user.rb"], ... }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The instance caches constant resolution results, so reusing the same instance across multiple `build` calls is efficient.
|
|
146
|
+
|
|
147
|
+
#### Options
|
|
148
|
+
|
|
149
|
+
| Option | Type | Default | Description |
|
|
150
|
+
|---|---|---|---|
|
|
151
|
+
| `root` | String or Pathname | required | Absolute project root. Only resolved files under this path are included. |
|
|
152
|
+
| `ignored_paths` | String or Array<String> | `[]` | Files or directories to exclude from the graph and recursive traversal. |
|
|
153
|
+
| `*patterns` (on `build`) | String(s) or Array | required | File paths or globs to traverse. Relative paths are expanded against `root`. |
|
|
154
|
+
|
|
155
|
+
#### How it works
|
|
156
|
+
|
|
157
|
+
- `build` traverses reachable files and stores a direct dependency graph
|
|
158
|
+
- `dependencies` returns direct dependencies for a file
|
|
159
|
+
- `transitive_dependencies` computes and caches the transitive closure lazily
|
|
160
|
+
- Constant resolution results are cached and reused across `build` calls
|
|
161
|
+
- Resolves each reference from most-specific lexical candidate to least-specific
|
|
162
|
+
- Uses `const_defined?` and `const_source_location` to resolve literal constant references to source files
|
|
163
|
+
|
|
164
|
+
This is intended for a booted application process. It requires constants to be eager-loaded. It will not see dynamic constant lookups that are not expressed as literal constants in the source.
|
|
165
|
+
|
|
124
166
|
## Writing custom trackers
|
|
125
167
|
|
|
126
168
|
There are two approaches: a minimal custom tracker, or inheriting from `AbstractTracker`.
|
|
@@ -167,7 +209,7 @@ class MyTracker < FastCov::AbstractTracker
|
|
|
167
209
|
|
|
168
210
|
module MyPatch
|
|
169
211
|
def some_method(...)
|
|
170
|
-
MyTracker.record
|
|
212
|
+
MyTracker.record { some_file_path }
|
|
171
213
|
super
|
|
172
214
|
end
|
|
173
215
|
end
|
|
@@ -52,11 +52,21 @@ module FastCov
|
|
|
52
52
|
self
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
def build
|
|
56
|
+
raise ArgumentError, "build requires a block" unless block_given?
|
|
57
|
+
raise "CoverageMap is already started" if @started
|
|
58
|
+
|
|
59
|
+
start
|
|
60
|
+
begin
|
|
61
|
+
yield
|
|
62
|
+
ensure
|
|
63
|
+
result = stop
|
|
59
64
|
end
|
|
65
|
+
result
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def start
|
|
69
|
+
return self if @started
|
|
60
70
|
|
|
61
71
|
begin
|
|
62
72
|
@native_coverage = Coverage.new(
|
|
@@ -72,17 +82,7 @@ module FastCov
|
|
|
72
82
|
raise
|
|
73
83
|
end
|
|
74
84
|
|
|
75
|
-
|
|
76
|
-
result = nil
|
|
77
|
-
begin
|
|
78
|
-
yield
|
|
79
|
-
ensure
|
|
80
|
-
result = stop
|
|
81
|
-
end
|
|
82
|
-
result
|
|
83
|
-
else
|
|
84
|
-
self
|
|
85
|
-
end
|
|
85
|
+
self
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def stop
|
|
@@ -142,7 +142,7 @@ module FastCov
|
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
def absolute_path?(path)
|
|
145
|
-
File.absolute_path?(path
|
|
145
|
+
File.absolute_path?(path)
|
|
146
146
|
end
|
|
147
147
|
|
|
148
148
|
def normalize_path(path)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module FastCov
|
|
6
|
+
class StaticMap
|
|
7
|
+
module ReferenceExtractor
|
|
8
|
+
class << self
|
|
9
|
+
def extract(filename)
|
|
10
|
+
result = Prism.parse_file(filename)
|
|
11
|
+
return FastCov::StaticMap::EMPTY_ARRAY unless result.success?
|
|
12
|
+
|
|
13
|
+
reference_groups = []
|
|
14
|
+
seen_groups = {}
|
|
15
|
+
collect_constants(result.value, reference_groups, seen_groups, [])
|
|
16
|
+
|
|
17
|
+
reference_groups.map do |group|
|
|
18
|
+
group.map { |const_name| -const_name }.freeze
|
|
19
|
+
end.freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def collect_constants(node, reference_groups, seen_groups, nesting_prefixes)
|
|
25
|
+
case node
|
|
26
|
+
when Prism::ModuleNode
|
|
27
|
+
module_name = constant_name_for_nesting(node.constant_path)
|
|
28
|
+
next_prefixes = next_nesting_prefixes(nesting_prefixes, module_name)
|
|
29
|
+
node.body&.compact_child_nodes&.each do |child|
|
|
30
|
+
collect_constants(child, reference_groups, seen_groups, next_prefixes)
|
|
31
|
+
end
|
|
32
|
+
return
|
|
33
|
+
when Prism::ClassNode
|
|
34
|
+
class_name = constant_name_for_nesting(node.constant_path)
|
|
35
|
+
next_prefixes = next_nesting_prefixes(nesting_prefixes, class_name)
|
|
36
|
+
add_with_nesting(node.superclass, reference_groups, seen_groups, nesting_prefixes) if node.superclass
|
|
37
|
+
node.body&.compact_child_nodes&.each do |child|
|
|
38
|
+
collect_constants(child, reference_groups, seen_groups, next_prefixes)
|
|
39
|
+
end
|
|
40
|
+
return
|
|
41
|
+
when Prism::SingletonClassNode
|
|
42
|
+
node.body&.compact_child_nodes&.each do |child|
|
|
43
|
+
collect_constants(child, reference_groups, seen_groups, nesting_prefixes)
|
|
44
|
+
end
|
|
45
|
+
return
|
|
46
|
+
when Prism::ConstantPathNode
|
|
47
|
+
add_with_nesting(node, reference_groups, seen_groups, nesting_prefixes)
|
|
48
|
+
return
|
|
49
|
+
when Prism::ConstantReadNode
|
|
50
|
+
add_reference_group(
|
|
51
|
+
expand_with_nesting(node.name.to_s, nesting_prefixes),
|
|
52
|
+
reference_groups,
|
|
53
|
+
seen_groups
|
|
54
|
+
)
|
|
55
|
+
return
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
node.compact_child_nodes.each do |child|
|
|
59
|
+
collect_constants(child, reference_groups, seen_groups, nesting_prefixes)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def add_with_nesting(node, reference_groups, seen_groups, nesting_prefixes)
|
|
64
|
+
candidates = candidates_for_node(node, nesting_prefixes)
|
|
65
|
+
add_reference_group(candidates, reference_groups, seen_groups)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def candidates_for_node(node, nesting_prefixes)
|
|
69
|
+
case node
|
|
70
|
+
when Prism::ConstantPathNode
|
|
71
|
+
path = resolve_constant_path(node)
|
|
72
|
+
return FastCov::StaticMap::EMPTY_ARRAY unless path
|
|
73
|
+
|
|
74
|
+
if path.start_with?("::")
|
|
75
|
+
[path.delete_prefix("::")]
|
|
76
|
+
else
|
|
77
|
+
expand_with_nesting(path, nesting_prefixes)
|
|
78
|
+
end
|
|
79
|
+
when Prism::ConstantReadNode
|
|
80
|
+
expand_with_nesting(node.name.to_s, nesting_prefixes)
|
|
81
|
+
else
|
|
82
|
+
FastCov::StaticMap::EMPTY_ARRAY
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def add_reference_group(candidates, reference_groups, seen_groups)
|
|
87
|
+
return if candidates.empty?
|
|
88
|
+
|
|
89
|
+
key = candidates.join("\0")
|
|
90
|
+
return if seen_groups[key]
|
|
91
|
+
|
|
92
|
+
seen_groups[key] = true
|
|
93
|
+
reference_groups << candidates
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def expand_with_nesting(const_name, nesting_prefixes)
|
|
97
|
+
candidates = []
|
|
98
|
+
seen_candidates = {}
|
|
99
|
+
|
|
100
|
+
nesting_prefixes.reverse_each do |prefix|
|
|
101
|
+
add_unique("#{prefix}::#{const_name}", candidates, seen_candidates)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
add_unique(const_name, candidates, seen_candidates)
|
|
105
|
+
candidates
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def next_nesting_prefixes(nesting_prefixes, nested_name)
|
|
109
|
+
return nesting_prefixes unless nested_name
|
|
110
|
+
|
|
111
|
+
if nesting_prefixes.empty?
|
|
112
|
+
[nested_name]
|
|
113
|
+
else
|
|
114
|
+
nesting_prefixes + ["#{nesting_prefixes.last}::#{nested_name}"]
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def constant_name_for_nesting(node)
|
|
119
|
+
case node
|
|
120
|
+
when Prism::ConstantPathNode
|
|
121
|
+
resolve_constant_path(node)&.delete_prefix("::")
|
|
122
|
+
when Prism::ConstantReadNode
|
|
123
|
+
node.name.to_s
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def add_unique(const_name, constants, seen)
|
|
128
|
+
return if seen[const_name]
|
|
129
|
+
|
|
130
|
+
seen[const_name] = true
|
|
131
|
+
constants << const_name
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def resolve_constant_path(node)
|
|
135
|
+
parts = []
|
|
136
|
+
current = node
|
|
137
|
+
|
|
138
|
+
while current.is_a?(Prism::ConstantPathNode)
|
|
139
|
+
parts.unshift(current.name.to_s)
|
|
140
|
+
current = current.parent
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
if current.is_a?(Prism::ConstantReadNode)
|
|
144
|
+
parts.unshift(current.name.to_s)
|
|
145
|
+
elsif current.nil?
|
|
146
|
+
return "::#{parts.join("::")}"
|
|
147
|
+
else
|
|
148
|
+
return nil
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
parts.join("::")
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FastCov
|
|
4
|
+
class StaticMap
|
|
5
|
+
EMPTY_ARRAY = [].freeze
|
|
6
|
+
|
|
7
|
+
autoload :ReferenceExtractor, File.expand_path("static_map/reference_extractor", __dir__)
|
|
8
|
+
|
|
9
|
+
def initialize(root:, ignored_paths: [], concurrency: Etc.nprocessors)
|
|
10
|
+
@root = share_path(root)
|
|
11
|
+
@root_prefix = -"#{@root}/"
|
|
12
|
+
@ignored_paths = normalize_ignored_paths(ignored_paths)
|
|
13
|
+
@concurrency = concurrency
|
|
14
|
+
@resolved_file_by_const = {}
|
|
15
|
+
@closure_by_file = {}
|
|
16
|
+
@graph = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def build(*patterns)
|
|
20
|
+
input_files = expand_files(patterns.flatten).select { |file| processable_file?(file) }
|
|
21
|
+
|
|
22
|
+
queue = input_files.dup
|
|
23
|
+
processed = {}
|
|
24
|
+
|
|
25
|
+
until queue.empty?
|
|
26
|
+
to_process = queue.reject { |f| processed[f] }
|
|
27
|
+
break if to_process.empty?
|
|
28
|
+
|
|
29
|
+
to_process.each { |f| processed[f] = true }
|
|
30
|
+
queue.clear
|
|
31
|
+
|
|
32
|
+
# Stage 1: Parse files (parallel)
|
|
33
|
+
parsed = parse_files(to_process)
|
|
34
|
+
|
|
35
|
+
# Stage 2: Resolve unique constants (sequential — GVL-bound)
|
|
36
|
+
resolve_candidates(parsed)
|
|
37
|
+
|
|
38
|
+
# Stage 3: Build graph edges, discover new files
|
|
39
|
+
parsed.each do |file, groups|
|
|
40
|
+
deps = resolve_dependencies(file, groups)
|
|
41
|
+
relative_file = relativize(file)
|
|
42
|
+
@graph[relative_file] = deps.empty? ? EMPTY_ARRAY : deps.map { |d| relativize(d) }.freeze
|
|
43
|
+
|
|
44
|
+
deps.each do |dep|
|
|
45
|
+
queue << dep unless processed[dep]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
input_files.flat_map { |file| dependencies(file) }.uniq
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def direct_graph
|
|
54
|
+
@graph
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def direct_dependencies(file)
|
|
58
|
+
@graph.fetch(relativize_input(file), EMPTY_ARRAY)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def dependencies(file)
|
|
62
|
+
file = relativize_input(file)
|
|
63
|
+
resolve_transitive_dependencies(file)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
attr_reader :closure_by_file, :ignored_paths, :resolved_file_by_const, :root
|
|
69
|
+
|
|
70
|
+
def parse_files(files)
|
|
71
|
+
if @concurrency > 1 && files.size > 1
|
|
72
|
+
parse_files_parallel(files)
|
|
73
|
+
else
|
|
74
|
+
parse_files_sequential(files)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def parse_files_sequential(files)
|
|
79
|
+
parsed = {}
|
|
80
|
+
files.each { |f| parsed[f] = ReferenceExtractor.extract(f) }
|
|
81
|
+
parsed
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def parse_files_parallel(files)
|
|
85
|
+
parsed = {}
|
|
86
|
+
mutex = Mutex.new
|
|
87
|
+
slice_size = [files.size / @concurrency + 1, 1].max
|
|
88
|
+
|
|
89
|
+
files.each_slice(slice_size).map do |slice|
|
|
90
|
+
Thread.new(slice) do |thread_files|
|
|
91
|
+
local = {}
|
|
92
|
+
thread_files.each { |f| local[f] = ReferenceExtractor.extract(f) }
|
|
93
|
+
mutex.synchronize { parsed.merge!(local) }
|
|
94
|
+
end
|
|
95
|
+
end.each(&:join)
|
|
96
|
+
|
|
97
|
+
parsed
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def resolve_candidates(parsed)
|
|
101
|
+
parsed.each_value do |groups|
|
|
102
|
+
groups.each do |candidates|
|
|
103
|
+
candidates.each do |const_name|
|
|
104
|
+
next if resolved_file_by_const.key?(const_name)
|
|
105
|
+
|
|
106
|
+
resolve_constant_file(const_name)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def resolve_dependencies(file, groups)
|
|
113
|
+
deps = {}
|
|
114
|
+
|
|
115
|
+
groups.each do |candidates|
|
|
116
|
+
resolved_file = resolve_reference_group(candidates)
|
|
117
|
+
next unless resolved_file
|
|
118
|
+
next if resolved_file == file
|
|
119
|
+
|
|
120
|
+
deps[resolved_file] = true
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
deps.empty? ? EMPTY_ARRAY : deps.keys.sort.freeze
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def resolve_reference_group(candidates)
|
|
127
|
+
candidates.each do |const_name|
|
|
128
|
+
file = resolved_file_by_const[const_name]
|
|
129
|
+
return file if file && include_path?(file)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def resolve_transitive_dependencies(file)
|
|
136
|
+
cached = closure_by_file[file]
|
|
137
|
+
return cached if cached
|
|
138
|
+
|
|
139
|
+
visiting = {}
|
|
140
|
+
stack = [[file, :enter]]
|
|
141
|
+
|
|
142
|
+
until stack.empty?
|
|
143
|
+
current_file, state = stack.pop
|
|
144
|
+
|
|
145
|
+
if state == :exit
|
|
146
|
+
dependencies = {}
|
|
147
|
+
|
|
148
|
+
@graph.fetch(current_file, EMPTY_ARRAY).each do |dependency_file|
|
|
149
|
+
dependencies[dependency_file] = true
|
|
150
|
+
|
|
151
|
+
closure_by_file.fetch(dependency_file, EMPTY_ARRAY).each do |transitive_dependency|
|
|
152
|
+
dependencies[transitive_dependency] = true
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
dependencies.delete(current_file)
|
|
157
|
+
closure_by_file[current_file] = dependencies.empty? ? EMPTY_ARRAY : dependencies.keys.sort.freeze
|
|
158
|
+
visiting.delete(current_file)
|
|
159
|
+
next
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
next if closure_by_file.key?(current_file)
|
|
163
|
+
next if visiting[current_file]
|
|
164
|
+
|
|
165
|
+
visiting[current_file] = true
|
|
166
|
+
stack << [current_file, :exit]
|
|
167
|
+
|
|
168
|
+
@graph.fetch(current_file, EMPTY_ARRAY).reverse_each do |dependency_file|
|
|
169
|
+
next if closure_by_file.key?(dependency_file)
|
|
170
|
+
next if visiting[dependency_file]
|
|
171
|
+
|
|
172
|
+
stack << [dependency_file, :enter]
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
closure_by_file.fetch(file, EMPTY_ARRAY)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def resolve_constant_file(const_name)
|
|
180
|
+
return nil unless constant_defined?(const_name)
|
|
181
|
+
|
|
182
|
+
source_location = Object.const_source_location(const_name)
|
|
183
|
+
file = source_location&.first
|
|
184
|
+
return nil unless file && File.file?(file)
|
|
185
|
+
|
|
186
|
+
resolved_file_by_const[const_name] = share_path(file)
|
|
187
|
+
rescue StandardError
|
|
188
|
+
nil
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def constant_defined?(const_name)
|
|
192
|
+
current = Object
|
|
193
|
+
|
|
194
|
+
const_name.split("::").each do |segment|
|
|
195
|
+
return false unless current.const_defined?(segment, false)
|
|
196
|
+
|
|
197
|
+
current = current.const_get(segment, false)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
true
|
|
201
|
+
rescue StandardError
|
|
202
|
+
false
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def include_path?(path)
|
|
206
|
+
return false unless FastCov::Utils.path_within?(path, root)
|
|
207
|
+
|
|
208
|
+
ignored_paths.none? { |ignored_path| FastCov::Utils.path_within?(path, ignored_path) }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def processable_file?(file)
|
|
212
|
+
File.file?(file) && include_path?(file)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def expand_files(patterns)
|
|
216
|
+
Array(patterns)
|
|
217
|
+
.flat_map do |pattern|
|
|
218
|
+
pattern = pattern.to_s
|
|
219
|
+
pattern = File.expand_path(pattern, root) unless File.absolute_path?(pattern)
|
|
220
|
+
Dir.glob(pattern)
|
|
221
|
+
end
|
|
222
|
+
.map { |path| share_path(path) }
|
|
223
|
+
.uniq
|
|
224
|
+
.sort
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def normalize_ignored_paths(ignored_paths)
|
|
228
|
+
Array(ignored_paths)
|
|
229
|
+
.compact
|
|
230
|
+
.map { |path| share_path(File.expand_path(path, root)) }
|
|
231
|
+
.uniq
|
|
232
|
+
.sort
|
|
233
|
+
.freeze
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def relativize(absolute_path)
|
|
237
|
+
share_string(absolute_path.delete_prefix(@root_prefix))
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def relativize_input(file)
|
|
241
|
+
path = file.to_s
|
|
242
|
+
if File.absolute_path?(path)
|
|
243
|
+
relativize(path)
|
|
244
|
+
else
|
|
245
|
+
share_string(path)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def share_path(path)
|
|
250
|
+
share_string(File.expand_path(path.to_s))
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def share_string(string)
|
|
254
|
+
-string.to_s
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
data/lib/fast_cov/version.rb
CHANGED
data/lib/fast_cov.rb
CHANGED
|
@@ -10,4 +10,5 @@ module FastCov
|
|
|
10
10
|
autoload :FileTracker, File.expand_path("fast_cov/trackers/file_tracker", __dir__)
|
|
11
11
|
autoload :FactoryBotTracker, File.expand_path("fast_cov/trackers/factory_bot_tracker", __dir__)
|
|
12
12
|
autoload :ConstGetTracker, File.expand_path("fast_cov/trackers/const_get_tracker", __dir__)
|
|
13
|
+
autoload :StaticMap, File.expand_path("fast_cov/static_map", __dir__)
|
|
13
14
|
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ngan Pham
|
|
@@ -85,6 +85,8 @@ files:
|
|
|
85
85
|
- lib/fast_cov/connected_dependencies.rb
|
|
86
86
|
- lib/fast_cov/coverage_map.rb
|
|
87
87
|
- lib/fast_cov/dev.rb
|
|
88
|
+
- lib/fast_cov/static_map.rb
|
|
89
|
+
- lib/fast_cov/static_map/reference_extractor.rb
|
|
88
90
|
- lib/fast_cov/trackers/abstract_tracker.rb
|
|
89
91
|
- lib/fast_cov/trackers/const_get_tracker.rb
|
|
90
92
|
- lib/fast_cov/trackers/factory_bot_tracker.rb
|