deep-cover 0.1.1
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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +127 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/cov +43 -0
- data/bin/gemcov +8 -0
- data/bin/selfcov +21 -0
- data/bin/setup +8 -0
- data/bin/testall +88 -0
- data/deep_cover.gemspec +44 -0
- data/exe/deep-cover +6 -0
- data/future_read_me.md +108 -0
- data/lib/deep-cover.rb +1 -0
- data/lib/deep_cover.rb +11 -0
- data/lib/deep_cover/analyser.rb +24 -0
- data/lib/deep_cover/analyser/base.rb +51 -0
- data/lib/deep_cover/analyser/branch.rb +20 -0
- data/lib/deep_cover/analyser/covered_code_source.rb +31 -0
- data/lib/deep_cover/analyser/function.rb +12 -0
- data/lib/deep_cover/analyser/ignore_uncovered.rb +19 -0
- data/lib/deep_cover/analyser/node.rb +11 -0
- data/lib/deep_cover/analyser/per_char.rb +20 -0
- data/lib/deep_cover/analyser/per_line.rb +23 -0
- data/lib/deep_cover/analyser/statement.rb +31 -0
- data/lib/deep_cover/analyser/subset.rb +24 -0
- data/lib/deep_cover/auto_run.rb +49 -0
- data/lib/deep_cover/autoload_tracker.rb +75 -0
- data/lib/deep_cover/backports.rb +9 -0
- data/lib/deep_cover/base.rb +55 -0
- data/lib/deep_cover/builtin_takeover.rb +2 -0
- data/lib/deep_cover/cli/debugger.rb +93 -0
- data/lib/deep_cover/cli/deep_cover.rb +49 -0
- data/lib/deep_cover/cli/instrumented_clone_reporter.rb +105 -0
- data/lib/deep_cover/config.rb +52 -0
- data/lib/deep_cover/core_ext/autoload_overrides.rb +40 -0
- data/lib/deep_cover/core_ext/coverage_replacement.rb +26 -0
- data/lib/deep_cover/core_ext/load_overrides.rb +24 -0
- data/lib/deep_cover/core_ext/require_overrides.rb +36 -0
- data/lib/deep_cover/coverage.rb +198 -0
- data/lib/deep_cover/covered_code.rb +138 -0
- data/lib/deep_cover/custom_requirer.rb +93 -0
- data/lib/deep_cover/node.rb +8 -0
- data/lib/deep_cover/node/arguments.rb +50 -0
- data/lib/deep_cover/node/assignments.rb +250 -0
- data/lib/deep_cover/node/base.rb +99 -0
- data/lib/deep_cover/node/begin.rb +25 -0
- data/lib/deep_cover/node/block.rb +53 -0
- data/lib/deep_cover/node/boolean.rb +22 -0
- data/lib/deep_cover/node/branch.rb +28 -0
- data/lib/deep_cover/node/case.rb +94 -0
- data/lib/deep_cover/node/collections.rb +21 -0
- data/lib/deep_cover/node/const.rb +10 -0
- data/lib/deep_cover/node/def.rb +38 -0
- data/lib/deep_cover/node/empty_body.rb +21 -0
- data/lib/deep_cover/node/exceptions.rb +74 -0
- data/lib/deep_cover/node/if.rb +36 -0
- data/lib/deep_cover/node/keywords.rb +84 -0
- data/lib/deep_cover/node/literals.rb +77 -0
- data/lib/deep_cover/node/loops.rb +72 -0
- data/lib/deep_cover/node/mixin/can_augment_children.rb +65 -0
- data/lib/deep_cover/node/mixin/check_completion.rb +16 -0
- data/lib/deep_cover/node/mixin/child_can_be_empty.rb +25 -0
- data/lib/deep_cover/node/mixin/executed_after_children.rb +13 -0
- data/lib/deep_cover/node/mixin/execution_location.rb +56 -0
- data/lib/deep_cover/node/mixin/flow_accounting.rb +63 -0
- data/lib/deep_cover/node/mixin/has_child.rb +138 -0
- data/lib/deep_cover/node/mixin/has_child_handler.rb +73 -0
- data/lib/deep_cover/node/mixin/has_tracker.rb +44 -0
- data/lib/deep_cover/node/mixin/is_statement.rb +18 -0
- data/lib/deep_cover/node/mixin/rewriting.rb +32 -0
- data/lib/deep_cover/node/mixin/wrapper.rb +13 -0
- data/lib/deep_cover/node/module.rb +64 -0
- data/lib/deep_cover/node/root.rb +18 -0
- data/lib/deep_cover/node/send.rb +83 -0
- data/lib/deep_cover/node/splat.rb +13 -0
- data/lib/deep_cover/node/variables.rb +14 -0
- data/lib/deep_cover/parser_ext/range.rb +40 -0
- data/lib/deep_cover/reporter.rb +6 -0
- data/lib/deep_cover/reporter/istanbul.rb +151 -0
- data/lib/deep_cover/tools.rb +18 -0
- data/lib/deep_cover/tools/builtin_coverage.rb +50 -0
- data/lib/deep_cover/tools/camelize.rb +8 -0
- data/lib/deep_cover/tools/dump_covered_code.rb +32 -0
- data/lib/deep_cover/tools/execute_sample.rb +23 -0
- data/lib/deep_cover/tools/format.rb +16 -0
- data/lib/deep_cover/tools/format_char_cover.rb +18 -0
- data/lib/deep_cover/tools/format_generated_code.rb +25 -0
- data/lib/deep_cover/tools/number_lines.rb +18 -0
- data/lib/deep_cover/tools/our_coverage.rb +9 -0
- data/lib/deep_cover/tools/require_relative_dir.rb +10 -0
- data/lib/deep_cover/tools/silence_warnings.rb +15 -0
- data/lib/deep_cover/version.rb +3 -0
- metadata +326 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
# We need to override autoload, because MRI has special behaviors associated with it
|
2
|
+
# that we can't reuse, hence we need to do workarounds.
|
3
|
+
#
|
4
|
+
# Basically, when trying to use a constant set to be autoloaded in an optionnal way, like:
|
5
|
+
# * module A; ...; end
|
6
|
+
# * A ||= 1
|
7
|
+
# When autoloading the file, the above won't work and will raise a "uninitialized constant A"
|
8
|
+
# because ruby doesn't understand that custom require is currently requiring the correct file.
|
9
|
+
#
|
10
|
+
# Our solution is to track autoloads ourself, and when requiring a path that has autoloads,
|
11
|
+
# we remove the autoloads from the constants first.
|
12
|
+
|
13
|
+
require 'binding_of_caller'
|
14
|
+
|
15
|
+
class << Kernel
|
16
|
+
alias_method :autoload_without_coverage, :autoload
|
17
|
+
def autoload(name, path)
|
18
|
+
mod = binding.of_caller(1).eval('Module.nesting').first || Object
|
19
|
+
DeepCover.autoload_tracker.add(mod, name, path)
|
20
|
+
mod.autoload_without_coverage(name, path)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module Kernel
|
25
|
+
alias_method :autoload_without_coverage, :autoload
|
26
|
+
def autoload(name, path)
|
27
|
+
mod = binding.of_caller(1).eval('Module.nesting').first || Object
|
28
|
+
DeepCover.autoload_tracker.add(mod, name, path)
|
29
|
+
mod.autoload_without_coverage(name, path)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
class Module
|
35
|
+
alias_method :autoload_without_coverage, :autoload
|
36
|
+
def autoload(name, path)
|
37
|
+
DeepCover.autoload_tracker.add(self, name, path)
|
38
|
+
autoload_without_coverage(name, path)
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# This is a complete replacement for the builtin Coverage module of Ruby
|
2
|
+
|
3
|
+
require 'coverage'
|
4
|
+
BuiltinCoverage = Coverage
|
5
|
+
Object.send(:remove_const, 'Coverage')
|
6
|
+
|
7
|
+
module Coverage
|
8
|
+
def self.start
|
9
|
+
@started = true
|
10
|
+
DeepCover.start
|
11
|
+
DeepCover.coverage.reset
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.result
|
15
|
+
raise 'coverage measurement is not enabled' unless @started
|
16
|
+
@started = false
|
17
|
+
self.peek
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.peek
|
21
|
+
results = DeepCover.coverage.covered_codes.map do |filename, covered_code|
|
22
|
+
[filename, covered_code.line_coverage(allow_partial: false)]
|
23
|
+
end
|
24
|
+
Hash[results]
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# These are the monkeypatches to replace the default #load in order
|
2
|
+
# to instrument the code before it gets run.
|
3
|
+
# For now, this is not used, and may never be. The tracking and reporting for things can might be
|
4
|
+
# loaded multiple times can be complex and is beyond the current scope of the project.
|
5
|
+
|
6
|
+
class << Kernel
|
7
|
+
alias_method :load_without_coverage, :load
|
8
|
+
def load(path, wrap = false)
|
9
|
+
return load_without_coverage(path, wrap) if wrap
|
10
|
+
|
11
|
+
result = DeepCover.custom_requirer.load(path)
|
12
|
+
if [:not_found, :cover_failed, :not_supported].include?(result)
|
13
|
+
load_without_coverage(path)
|
14
|
+
else
|
15
|
+
result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Kernel
|
21
|
+
def load(path, wrap = false)
|
22
|
+
Kernel.require(path, wrap)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# These are the monkeypatches to replace the default #require and
|
2
|
+
# #require_relative in order to instrument the code before it gets run.
|
3
|
+
|
4
|
+
class << Kernel
|
5
|
+
alias_method :require_without_coverage, :require
|
6
|
+
def require(path)
|
7
|
+
result = DeepCover.custom_requirer.require(path)
|
8
|
+
if [:not_found, :cover_failed, :not_supported].include?(result)
|
9
|
+
require_without_coverage(path)
|
10
|
+
else
|
11
|
+
result
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def require_relative(path)
|
16
|
+
base = caller(1..1).first[/[^:]+/]
|
17
|
+
raise LoadError, "cannot infer basepath" unless base
|
18
|
+
base = File.dirname(base)
|
19
|
+
|
20
|
+
require(File.absolute_path(path, base))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module Kernel
|
25
|
+
def require(path)
|
26
|
+
Kernel.require(path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def require_relative(path)
|
30
|
+
base = caller(1..1).first[/[^:]+/]
|
31
|
+
raise LoadError, "cannot infer basepath" unless base
|
32
|
+
base = File.dirname(base)
|
33
|
+
|
34
|
+
require(File.absolute_path(path, base))
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
module DeepCover
|
2
|
+
require 'parser'
|
3
|
+
silence_warnings do
|
4
|
+
require 'parser/current'
|
5
|
+
end
|
6
|
+
require 'pry'
|
7
|
+
require 'pathname'
|
8
|
+
require_relative 'covered_code'
|
9
|
+
require 'securerandom'
|
10
|
+
|
11
|
+
# A collection of CoveredCode
|
12
|
+
class Coverage
|
13
|
+
include Enumerable
|
14
|
+
|
15
|
+
def initialize(**options)
|
16
|
+
@covered_codes = {}
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
def covered_codes
|
21
|
+
@covered_codes.dup
|
22
|
+
end
|
23
|
+
|
24
|
+
def reset
|
25
|
+
@covered_codes = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def line_coverage(filename, **options)
|
29
|
+
covered_code(filename).line_coverage(**options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def covered_code(path)
|
33
|
+
raise 'path must be an absolute path' unless Pathname.new(path).absolute?
|
34
|
+
@covered_codes[path] ||= CoveredCode.new(path: path, **@options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def each
|
38
|
+
return to_enum unless block_given?
|
39
|
+
@covered_codes.each{|_path, covered_code| yield covered_code}
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_istanbul(**options)
|
44
|
+
map do |covered_code|
|
45
|
+
next {} unless covered_code.has_executed?
|
46
|
+
covered_code.to_istanbul(**options)
|
47
|
+
end.inject(:merge)
|
48
|
+
end
|
49
|
+
|
50
|
+
def output_istanbul(dir: '.', name: ".nyc_output", **options)
|
51
|
+
path = Pathname.new(dir).expand_path.join(name)
|
52
|
+
path.mkpath
|
53
|
+
path.each_child(&:delete)
|
54
|
+
path.join('deep_cover.json').write(JSON.pretty_generate(to_istanbul(**options)))
|
55
|
+
path
|
56
|
+
end
|
57
|
+
|
58
|
+
def report_istanbul(output: nil, **options)
|
59
|
+
dir = output_istanbul(**options).dirname
|
60
|
+
if output
|
61
|
+
output = File.expand_path(output)
|
62
|
+
html = "--reporter=html --report-dir='#{output}' && open '#{output}/index.html'"
|
63
|
+
end
|
64
|
+
`cd #{dir} && nyc report --reporter=text #{html}`
|
65
|
+
end
|
66
|
+
|
67
|
+
def basic_report
|
68
|
+
missing = map do |covered_code|
|
69
|
+
if covered_code.has_executed?
|
70
|
+
missed = covered_code.line_coverage.each_with_index.map do |line_cov, line_index|
|
71
|
+
line_index + 1 if line_cov == 0
|
72
|
+
end.compact
|
73
|
+
else
|
74
|
+
missed = ['all']
|
75
|
+
end
|
76
|
+
[covered_code.buffer.name, missed] unless missed.empty?
|
77
|
+
end.compact.to_h
|
78
|
+
missing.map do |path, lines|
|
79
|
+
"#{File.basename(path)}: #{lines.join(', ')}"
|
80
|
+
end.join("\n")
|
81
|
+
end
|
82
|
+
|
83
|
+
def report(**options)
|
84
|
+
if Reporter::Istanbul.available?
|
85
|
+
report_istanbul(**options)
|
86
|
+
else
|
87
|
+
warn "nyc not available. Please install `nyc` using `yarn global add nyc` or `npm i nyc -g`"
|
88
|
+
basic_report
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.load(dest_path, dirname = 'deep_cover')
|
93
|
+
Persistence.new(dest_path, dirname).load
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.saved?(dest_path, dirname = 'deep_cover')
|
97
|
+
Persistence.new(dest_path, dirname).saved?
|
98
|
+
end
|
99
|
+
|
100
|
+
def save(dest_path, dirname = 'deep_cover')
|
101
|
+
Persistence.new(dest_path, dirname).save(self)
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def save_trackers(dest_path, dirname = 'deep_cover')
|
106
|
+
Persistence.new(dest_path, dirname).save_trackers(tracker_global)
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
def tracker_global
|
111
|
+
@options[:tracker_global]
|
112
|
+
end
|
113
|
+
|
114
|
+
class Persistence
|
115
|
+
BASENAME = 'coverage.dc'
|
116
|
+
TRACKER_TEMPLATE = 'trackers%{unique}.dct'
|
117
|
+
|
118
|
+
attr_reader :dir_path
|
119
|
+
def initialize(dest_path, dirname)
|
120
|
+
@dir_path = Pathname(dest_path).join(dirname).expand_path
|
121
|
+
end
|
122
|
+
|
123
|
+
def load
|
124
|
+
saved?
|
125
|
+
load_trackers
|
126
|
+
load_coverage
|
127
|
+
end
|
128
|
+
|
129
|
+
def save(coverage)
|
130
|
+
create_if_needed
|
131
|
+
delete_trackers
|
132
|
+
save_coverage(coverage)
|
133
|
+
end
|
134
|
+
|
135
|
+
def save_trackers(global)
|
136
|
+
saved?
|
137
|
+
basename = TRACKER_TEMPLATE % {unique: SecureRandom.urlsafe_base64}
|
138
|
+
dir_path.join(basename).binwrite(Marshal.dump({
|
139
|
+
version: DeepCover::VERSION,
|
140
|
+
global: global,
|
141
|
+
trackers: eval(global),
|
142
|
+
}))
|
143
|
+
end
|
144
|
+
|
145
|
+
def saved?
|
146
|
+
raise "Can't find folder '#{dir_path}'" unless dir_path.exist?
|
147
|
+
self
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def create_if_needed
|
153
|
+
dir_path.mkpath
|
154
|
+
end
|
155
|
+
|
156
|
+
def save_coverage(coverage)
|
157
|
+
dir_path.join(BASENAME).binwrite(Marshal.dump({
|
158
|
+
version: DeepCover::VERSION,
|
159
|
+
coverage: coverage,
|
160
|
+
}))
|
161
|
+
end
|
162
|
+
|
163
|
+
def load_coverage
|
164
|
+
Marshal.load(dir_path.join(BASENAME).binread).tap do |version: raise, coverage: raise|
|
165
|
+
raise "dump version mismatch: #{deep_cover}, currently #{DeepCover::VERSION}" unless version == DeepCover::VERSION
|
166
|
+
return coverage
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def load_trackers
|
171
|
+
tracker_files.each do |full_path|
|
172
|
+
Marshal.load(full_path.binread).tap do |version: raise, global: raise, trackers: raise|
|
173
|
+
raise "dump version mismatch: #{deep_cover}, currently #{DeepCover::VERSION}" unless version == DeepCover::VERSION
|
174
|
+
merge_trackers(eval("#{global} ||= {}"), trackers)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def merge_trackers(hash, to_merge)
|
180
|
+
hash.merge!(to_merge) do |_key, current, to_add|
|
181
|
+
unless current.size == 0 || current.size == to_add.size
|
182
|
+
warn "Merging trackers of different sizes: #{current.size} vs #{to_add.size}"
|
183
|
+
end
|
184
|
+
to_add.zip(current).map{|a, b| a+b}
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def tracker_files
|
189
|
+
basename = TRACKER_TEMPLATE % { unique: '*' }
|
190
|
+
Pathname.glob(dir_path.join(basename))
|
191
|
+
end
|
192
|
+
|
193
|
+
def delete_trackers
|
194
|
+
tracker_files.each(&:delete)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module DeepCover
|
2
|
+
class CoveredCode
|
3
|
+
attr_accessor :covered_source, :buffer, :tracker_global, :local_var
|
4
|
+
@@counter = 0
|
5
|
+
@@globals = Hash.new{|h, global| h[global] = eval("#{global} ||= {}") }
|
6
|
+
|
7
|
+
def initialize(path: nil, source: nil, lineno: nil, tracker_global: '$_cov', local_var: '_temp')
|
8
|
+
raise "Must provide either path or source" unless path || source
|
9
|
+
|
10
|
+
@buffer = ::Parser::Source::Buffer.new(path)
|
11
|
+
if source
|
12
|
+
@buffer.source = source
|
13
|
+
else
|
14
|
+
@buffer.read
|
15
|
+
end
|
16
|
+
@lineno = lineno
|
17
|
+
@tracker_count = 0
|
18
|
+
@tracker_global = tracker_global
|
19
|
+
@local_var = local_var
|
20
|
+
@covered_source = instrument_source
|
21
|
+
end
|
22
|
+
|
23
|
+
def path
|
24
|
+
@buffer.name || "(source: '#{@buffer.source[0..20]}...')"
|
25
|
+
end
|
26
|
+
|
27
|
+
def name
|
28
|
+
@buffer.name ? File.basename(@buffer.name) : "(source)"
|
29
|
+
end
|
30
|
+
|
31
|
+
def nb_lines
|
32
|
+
@nb_lines ||= begin
|
33
|
+
lines = buffer.source_lines
|
34
|
+
if lines.size == 0
|
35
|
+
0
|
36
|
+
else
|
37
|
+
lines.size - (lines.last.empty? ? 1 : 0)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def execute_code(binding: DeepCover::GLOBAL_BINDING.dup)
|
43
|
+
return if has_executed?
|
44
|
+
global[nb] = Array.new(@tracker_count, 0)
|
45
|
+
eval(@covered_source, binding, @buffer.name || '<raw_code>', @lineno || 1)
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def cover
|
50
|
+
must_have_executed
|
51
|
+
global[nb]
|
52
|
+
end
|
53
|
+
|
54
|
+
def line_coverage(**options)
|
55
|
+
must_have_executed
|
56
|
+
Analyser::PerLine.new(self, **options).results
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_istanbul(**options)
|
60
|
+
must_have_executed
|
61
|
+
Reporter::Istanbul.new(self, **options).convert
|
62
|
+
end
|
63
|
+
|
64
|
+
def char_cover(**options)
|
65
|
+
must_have_executed
|
66
|
+
Analyser::PerChar.new(self, **options).results
|
67
|
+
end
|
68
|
+
|
69
|
+
def nb
|
70
|
+
@nb ||= (@@counter += 1)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns a range of tracker ids
|
74
|
+
def allocate_trackers(nb_needed)
|
75
|
+
prev = @tracker_count
|
76
|
+
@tracker_count += nb_needed
|
77
|
+
prev...@tracker_count
|
78
|
+
end
|
79
|
+
|
80
|
+
def tracker_source(tracker_id)
|
81
|
+
"#{tracker_global}[#{nb}][#{tracker_id}]+=1"
|
82
|
+
end
|
83
|
+
|
84
|
+
def trackers_setup_source
|
85
|
+
"(#{tracker_global}||={})[#{nb}]||=Array.new(#{@tracker_count},0)"
|
86
|
+
end
|
87
|
+
|
88
|
+
def tracker_hits(tracker_id)
|
89
|
+
cover.fetch(tracker_id)
|
90
|
+
end
|
91
|
+
|
92
|
+
def covered_ast
|
93
|
+
root.main
|
94
|
+
end
|
95
|
+
|
96
|
+
def root
|
97
|
+
@root ||= begin
|
98
|
+
ast = Parser::CurrentRuby.new.parse(@buffer)
|
99
|
+
Node::Root.new(ast, self)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def each_node(*args, &block)
|
104
|
+
covered_ast.each_node(*args, &block)
|
105
|
+
end
|
106
|
+
|
107
|
+
def instrument_source
|
108
|
+
rewriter = ::Parser::Source::Rewriter.new(@buffer)
|
109
|
+
covered_ast.each_node do |node|
|
110
|
+
prefix, suffix = node.rewrite_prefix_suffix
|
111
|
+
unless prefix.empty?
|
112
|
+
expression = node.expression
|
113
|
+
prefix = yield prefix, node, expression.begin, :prefix if block_given?
|
114
|
+
rewriter.insert_before_multi expression, prefix rescue binding.pry
|
115
|
+
end
|
116
|
+
unless suffix.empty?
|
117
|
+
expression = node.expression
|
118
|
+
suffix = yield suffix, node, expression.end, :suffix if block_given?
|
119
|
+
rewriter.insert_after_multi expression, suffix
|
120
|
+
end
|
121
|
+
end
|
122
|
+
rewriter.process
|
123
|
+
end
|
124
|
+
|
125
|
+
def has_executed?
|
126
|
+
global[nb] != nil
|
127
|
+
end
|
128
|
+
|
129
|
+
protected
|
130
|
+
def global
|
131
|
+
@@globals[tracker_global]
|
132
|
+
end
|
133
|
+
|
134
|
+
def must_have_executed
|
135
|
+
raise "cover for #{buffer.name} not available, file wasn't executed" unless has_executed?
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|