deep-cover 0.5.2 → 0.5.3
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 +5 -5
- data/.deep_cover.rb +8 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +15 -1
- data/.travis.yml +1 -0
- data/README.md +30 -1
- data/Rakefile +10 -1
- data/bin/cov +1 -1
- data/deep_cover.gemspec +4 -5
- data/exe/deep-cover +5 -3
- data/lib/deep_cover.rb +1 -1
- data/lib/deep_cover/analyser/node.rb +1 -1
- data/lib/deep_cover/analyser/ruby25_like_branch.rb +209 -0
- data/lib/deep_cover/auto_run.rb +19 -19
- data/lib/deep_cover/autoload_tracker.rb +181 -44
- data/lib/deep_cover/backports.rb +3 -1
- data/lib/deep_cover/base.rb +13 -8
- data/lib/deep_cover/basics.rb +1 -1
- data/lib/deep_cover/cli/debugger.rb +2 -2
- data/lib/deep_cover/cli/instrumented_clone_reporter.rb +21 -8
- data/lib/deep_cover/cli/runner.rb +126 -0
- data/lib/deep_cover/config_setter.rb +1 -0
- data/lib/deep_cover/core_ext/autoload_overrides.rb +82 -14
- data/lib/deep_cover/core_ext/coverage_replacement.rb +34 -5
- data/lib/deep_cover/core_ext/exec_callbacks.rb +27 -0
- data/lib/deep_cover/core_ext/load_overrides.rb +4 -6
- data/lib/deep_cover/core_ext/require_overrides.rb +1 -3
- data/lib/deep_cover/coverage.rb +105 -2
- data/lib/deep_cover/coverage/analysis.rb +30 -28
- data/lib/deep_cover/coverage/persistence.rb +60 -70
- data/lib/deep_cover/covered_code.rb +16 -49
- data/lib/deep_cover/custom_requirer.rb +112 -51
- data/lib/deep_cover/load.rb +10 -6
- data/lib/deep_cover/memoize.rb +1 -3
- data/lib/deep_cover/module_override.rb +7 -0
- data/lib/deep_cover/node/assignments.rb +2 -1
- data/lib/deep_cover/node/base.rb +6 -6
- data/lib/deep_cover/node/block.rb +10 -8
- data/lib/deep_cover/node/case.rb +3 -3
- data/lib/deep_cover/node/collections.rb +8 -0
- data/lib/deep_cover/node/if.rb +19 -3
- data/lib/deep_cover/node/literals.rb +28 -7
- data/lib/deep_cover/node/mixin/can_augment_children.rb +4 -4
- data/lib/deep_cover/node/mixin/child_can_be_empty.rb +1 -1
- data/lib/deep_cover/node/mixin/filters.rb +6 -2
- data/lib/deep_cover/node/mixin/has_child.rb +8 -8
- data/lib/deep_cover/node/mixin/has_child_handler.rb +3 -3
- data/lib/deep_cover/node/mixin/has_tracker.rb +7 -3
- data/lib/deep_cover/node/root.rb +1 -1
- data/lib/deep_cover/node/send.rb +53 -7
- data/lib/deep_cover/node/short_circuit.rb +11 -3
- data/lib/deep_cover/parser_ext/range.rb +11 -27
- data/lib/deep_cover/problem_with_diagnostic.rb +1 -1
- data/lib/deep_cover/reporter.rb +0 -1
- data/lib/deep_cover/reporter/base.rb +68 -0
- data/lib/deep_cover/reporter/html.rb +1 -1
- data/lib/deep_cover/reporter/html/index.rb +4 -8
- data/lib/deep_cover/reporter/html/site.rb +10 -18
- data/lib/deep_cover/reporter/html/source.rb +3 -3
- data/lib/deep_cover/reporter/html/template/source.html.erb +1 -1
- data/lib/deep_cover/reporter/istanbul.rb +86 -56
- data/lib/deep_cover/reporter/text.rb +5 -13
- data/lib/deep_cover/reporter/{util/tree.rb → tree/util.rb} +19 -21
- data/lib/deep_cover/tools/blank.rb +25 -0
- data/lib/deep_cover/tools/builtin_coverage.rb +8 -8
- data/lib/deep_cover/tools/dump_covered_code.rb +2 -9
- data/lib/deep_cover/tools/execute_sample.rb +17 -6
- data/lib/deep_cover/tools/format_generated_code.rb +1 -1
- data/lib/deep_cover/tools/indent_string.rb +26 -0
- data/lib/deep_cover/tools/our_coverage.rb +2 -2
- data/lib/deep_cover/tools/strip_heredoc.rb +18 -0
- data/lib/deep_cover/tracker_bucket.rb +50 -0
- data/lib/deep_cover/tracker_hits_per_path.rb +35 -0
- data/lib/deep_cover/tracker_storage.rb +76 -0
- data/lib/deep_cover/tracker_storage_per_path.rb +34 -0
- data/lib/deep_cover/version.rb +1 -1
- data/lib/deep_cover_entry.rb +3 -0
- metadata +30 -37
- data/bin/gemcov +0 -8
- data/bin/selfcov +0 -21
- data/lib/deep_cover/cli/deep_cover.rb +0 -126
- data/lib/deep_cover/coverage/base.rb +0 -81
- data/lib/deep_cover/coverage/istanbul.rb +0 -34
- data/lib/deep_cover/tools/transform_keys.rb +0 -9
@@ -2,77 +2,214 @@
|
|
2
2
|
|
3
3
|
require 'weakref'
|
4
4
|
|
5
|
+
# TODO: if a constant is removed, AutoloadEntries should be removed
|
6
|
+
|
5
7
|
module DeepCover
|
6
8
|
class AutoloadTracker
|
7
|
-
|
8
|
-
|
9
|
+
AutoloadEntry = Struct.new(:weak_mod, :name, :target_path, :interceptor_path) do
|
10
|
+
# If the ref is dead, will return nil
|
11
|
+
# If the target is frozen, will warn and return nil
|
12
|
+
# Otherwise return the target
|
13
|
+
def mod_if_available
|
14
|
+
mod = weak_mod.__getobj__
|
15
|
+
if mod.frozen?
|
16
|
+
AutoloadTracker.warn_frozen_module(mod)
|
17
|
+
nil
|
18
|
+
else
|
19
|
+
mod
|
20
|
+
end
|
21
|
+
rescue WeakRef::RefError
|
22
|
+
nil
|
23
|
+
end
|
9
24
|
end
|
10
25
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
path += '.rb' if ext != '.rb'
|
16
|
-
|
17
|
-
pairs = @autoloaded_paths[path] ||= []
|
18
|
-
pairs << [WeakRef.new(const), name]
|
26
|
+
attr_reader :autoloads_by_basename, :interceptor_files_by_path
|
27
|
+
def initialize
|
28
|
+
@autoloads_by_basename = {}
|
29
|
+
@interceptor_files_by_path = {}
|
19
30
|
end
|
20
31
|
|
21
|
-
def
|
22
|
-
|
32
|
+
def autoload_path_for(mod, name, path)
|
33
|
+
interceptor_path = setup_interceptor_for(mod, name, path)
|
23
34
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
pairs
|
35
|
+
if DeepCover.custom_requirer.is_being_required?(path)
|
36
|
+
already_loaded_feature
|
37
|
+
else
|
38
|
+
interceptor_path
|
29
39
|
end
|
30
40
|
end
|
31
41
|
|
32
|
-
def
|
33
|
-
|
42
|
+
def possible_autoload_target?(requested_path)
|
43
|
+
basename = basename_without_extension(requested_path)
|
44
|
+
autoloads = @autoloads_by_basename[basename]
|
45
|
+
autoloads && !autoloads.empty?
|
46
|
+
end
|
47
|
+
|
48
|
+
def wrap_require(requested_path, absolute_path_found) # &block
|
49
|
+
entries = entries_for_target(requested_path, absolute_path_found)
|
34
50
|
|
35
51
|
begin
|
36
|
-
|
37
|
-
|
38
|
-
|
52
|
+
entries.each do |entry|
|
53
|
+
mod = entry.mod_if_available
|
54
|
+
next unless mod
|
55
|
+
mod.autoload_without_deep_cover(entry.name, already_loaded_feature)
|
39
56
|
end
|
40
57
|
|
41
58
|
yield
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
59
|
+
ensure
|
60
|
+
entries = entries_for_target(requested_path, absolute_path_found)
|
61
|
+
entries.each do |entry|
|
62
|
+
mod = entry.mod_if_available
|
63
|
+
next unless mod
|
64
|
+
# Putting the autoloads back back since we couldn't complete the require
|
65
|
+
mod.autoload_without_deep_cover(entry.name, entry.interceptor_path)
|
46
66
|
end
|
47
|
-
|
48
|
-
raise
|
49
67
|
end
|
50
68
|
end
|
51
69
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
70
|
+
# This is only used on MRI, so ObjectSpace is alright.
|
71
|
+
def initialize_autoloaded_paths(mods = ObjectSpace.each_object(Module)) # &do_autoload_block
|
72
|
+
mods.each do |mod|
|
73
|
+
# Module's constants are shared with Object. But if you set autoloads directly on Module, they
|
74
|
+
# appear on multiple classes. So just skip, Object will take care of those.
|
75
|
+
next if mod == Module
|
76
|
+
|
77
|
+
if mod.frozen?
|
78
|
+
if mod.constants.any? { |name| mod.autoload?(name) }
|
79
|
+
self.class.warn_frozen_module(mod)
|
59
80
|
end
|
81
|
+
next
|
82
|
+
end
|
83
|
+
|
84
|
+
mod.constants.each do |name|
|
85
|
+
path = mod.autoload?(name)
|
86
|
+
next unless path
|
87
|
+
interceptor_path = setup_interceptor_for(mod, name, path)
|
88
|
+
yield mod, name, interceptor_path
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# We need to remove the interceptor hooks, otherwise, the problem if manually requiring
|
94
|
+
# something that is autoloaded will cause issues.
|
95
|
+
def remove_interceptors # &do_autoload_block
|
96
|
+
@autoloads_by_basename.each do |basename, entries|
|
97
|
+
entries.each do |entry|
|
98
|
+
mod = entry.mod_if_available
|
99
|
+
next unless mod
|
100
|
+
# Module's constants are shared with Object. But if you set autoloads directly on Module, they
|
101
|
+
# appear on multiple classes. So just skip, Object will take care of those.
|
102
|
+
next if mod == Module
|
103
|
+
yield mod, entry.name, entry.target_path
|
60
104
|
end
|
61
105
|
end
|
106
|
+
|
107
|
+
@autoloaded_paths = {}
|
108
|
+
@interceptor_files_by_path = {}
|
109
|
+
end
|
110
|
+
|
111
|
+
class << self
|
112
|
+
attr_accessor :warned_for_frozen_module
|
113
|
+
end
|
114
|
+
self.warned_for_frozen_module = false
|
115
|
+
|
116
|
+
# Using frozen modules/classes is almost unheard of, but a warning makes things easier if someone does it
|
117
|
+
def self.warn_frozen_module(mod)
|
118
|
+
return if warned_for_frozen_module
|
119
|
+
self.warned_for_frozen_module ||= true
|
120
|
+
warn "There is an autoload on a frozen module/class: #{mod}, DeepCover cannot handle those, failure is probable. " \
|
121
|
+
"This warning won't be displayed again (even for different module/class)"
|
122
|
+
end
|
123
|
+
|
124
|
+
protected
|
125
|
+
|
126
|
+
def setup_interceptor_for(mod, name, path)
|
127
|
+
interceptor_path = autoload_interceptor_for(path)
|
128
|
+
entry = AutoloadEntry.new(WeakRef.new(mod), name, path, interceptor_path)
|
129
|
+
|
130
|
+
basename = basename_without_extension(path)
|
131
|
+
|
132
|
+
@autoloads_by_basename[basename] ||= []
|
133
|
+
@autoloads_by_basename[basename] << entry
|
134
|
+
interceptor_path
|
62
135
|
end
|
63
136
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
137
|
+
def entries_for_target(requested_path, absolute_path_found)
|
138
|
+
basename = basename_without_extension(requested_path)
|
139
|
+
autoloads = @autoloads_by_basename[basename] || []
|
140
|
+
|
141
|
+
if absolute_path_found
|
142
|
+
autoloads.select { |entry| entry_is_target?(entry, requested_path, absolute_path_found) }
|
143
|
+
elsif requested_path == File.absolute_path(requested_path)
|
144
|
+
[]
|
145
|
+
elsif requested_path.start_with?('./', '../')
|
146
|
+
[]
|
147
|
+
else
|
148
|
+
# We didn't find a path that goes through the $LOAD_PATH
|
149
|
+
# It's possible that RubyGems will actually add the $LOAD_PATH and require an actual file
|
150
|
+
# So we must make a best-guest for possible matches
|
151
|
+
requested_path_to_compare = without_extension(requested_path)
|
152
|
+
autoloads.select { |entry| requested_path_to_compare == without_extension(entry.target_path) }
|
69
153
|
end
|
70
154
|
end
|
71
155
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
156
|
+
def entry_is_target?(entry, requested_path, absolute_path_found)
|
157
|
+
return true if entry.target_path == requested_path
|
158
|
+
target_path_rb = with_rb_extension(entry.target_path)
|
159
|
+
return true if target_path_rb == requested_path
|
160
|
+
|
161
|
+
# Even though this is not efficient, it's safer to resolve entries' target_path each time
|
162
|
+
# instead of storing the result, in case subsequent changes to $LOAD_PATH gives different results
|
163
|
+
entry_absolute_path = DeepCover.custom_requirer.resolve_path(entry.target_path)
|
164
|
+
return true if entry_absolute_path == absolute_path_found
|
165
|
+
false
|
166
|
+
end
|
167
|
+
|
168
|
+
def basename_without_extension(path)
|
169
|
+
without_extension(File.basename(path))
|
170
|
+
end
|
171
|
+
|
172
|
+
def with_rb_extension(path)
|
173
|
+
path += '.rb' unless has_supported_extension?(path)
|
174
|
+
path
|
175
|
+
end
|
176
|
+
|
177
|
+
def without_extension(path)
|
178
|
+
path = path[0...-3] if has_supported_extension?(path)
|
179
|
+
path
|
180
|
+
end
|
181
|
+
|
182
|
+
def has_supported_extension?(path)
|
183
|
+
path.end_with?('.rb', '.so')
|
184
|
+
end
|
185
|
+
|
186
|
+
# It is not possible to simply remove an autoload. So, instead, we must change the
|
187
|
+
# autoload to an already loaded path.
|
188
|
+
# The autoload will be set back to what it was once the require returns. This is
|
189
|
+
# needed in case that required path wasn't the one that fulfilled the autoload, or
|
190
|
+
# if the constant and $LOADED_FEATURES gets removed, since in that situation,
|
191
|
+
# the autoload is supposed to be active again.
|
192
|
+
def already_loaded_feature
|
193
|
+
$LOADED_FEATURES.first
|
194
|
+
end
|
195
|
+
|
196
|
+
def autoload_interceptor_for(path)
|
197
|
+
existing_files = @interceptor_files_by_path[path] || []
|
198
|
+
reusable_file = existing_files.detect { |f| !$LOADED_FEATURES.include?(f.path) }
|
199
|
+
return reusable_file.path if reusable_file
|
200
|
+
|
201
|
+
new_file = Tempfile.new([File.basename(path), '.rb'])
|
202
|
+
# Need to store all the tempfiles so that they are not GCed, which would delete the files themselves.
|
203
|
+
# Keeping them by path allows us to reuse them.
|
204
|
+
@interceptor_files_by_path[path] ||= []
|
205
|
+
@interceptor_files_by_path[path] << new_file
|
206
|
+
new_file.write(<<-RUBY)
|
207
|
+
# Intermediary file for ruby's autoload made by deep-cover
|
208
|
+
require #{path.to_s.inspect}
|
209
|
+
RUBY
|
210
|
+
new_file.close
|
211
|
+
|
212
|
+
new_file.path
|
76
213
|
end
|
77
214
|
end
|
78
215
|
end
|
data/lib/deep_cover/backports.rb
CHANGED
@@ -12,9 +12,11 @@ class Pathname
|
|
12
12
|
def binwrite(*args)
|
13
13
|
File.binwrite(to_path, *args)
|
14
14
|
end unless method_defined? :binwrite
|
15
|
-
end
|
15
|
+
end # nocov
|
16
16
|
require 'backports/2.4.0/false_class/dup'
|
17
17
|
require 'backports/2.4.0/true_class/dup'
|
18
18
|
require 'backports/2.4.0/hash/transform_values'
|
19
19
|
require 'backports/2.4.0/enumerable/sum'
|
20
20
|
require 'backports/2.5.0/hash/slice'
|
21
|
+
require 'backports/2.5.0/hash/transform_keys'
|
22
|
+
require 'backports/2.5.0/kernel/yield_self'
|
data/lib/deep_cover/base.rb
CHANGED
@@ -3,27 +3,32 @@
|
|
3
3
|
module DeepCover
|
4
4
|
module Base
|
5
5
|
def running?
|
6
|
-
@started
|
6
|
+
@started ||= false # rubocop:disable Naming/MemoizedInstanceVariableName [#5648]
|
7
7
|
end
|
8
8
|
|
9
9
|
def start
|
10
|
-
return if
|
10
|
+
return if running?
|
11
11
|
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
12
|
-
#
|
12
|
+
# Autoloaded files are not supported on jruby. We need to use binding_of_caller
|
13
|
+
# And that appears to be unavailable in jruby.
|
13
14
|
else
|
14
15
|
require_relative 'core_ext/autoload_overrides'
|
15
16
|
AutoloadOverride.active = true
|
16
|
-
autoload_tracker.initialize_autoloaded_paths
|
17
|
+
autoload_tracker.initialize_autoloaded_paths { |mod, name, path| mod.autoload_without_deep_cover(name, path) }
|
17
18
|
end
|
18
19
|
require_relative 'core_ext/require_overrides'
|
19
20
|
RequireOverride.active = true
|
20
21
|
config # actualize configuration
|
22
|
+
@custom_requirer = nil
|
21
23
|
@started = true
|
22
24
|
end
|
23
25
|
|
24
26
|
def stop
|
25
27
|
require_relative 'core_ext/require_overrides'
|
26
|
-
|
28
|
+
if defined? AutoloadOverride
|
29
|
+
AutoloadOverride.active = false
|
30
|
+
autoload_tracker.remove_interceptors { |mod, name, path| mod.autoload_without_deep_cover(name, path) }
|
31
|
+
end
|
27
32
|
RequireOverride.active = false
|
28
33
|
@started = false
|
29
34
|
end
|
@@ -51,16 +56,16 @@ module DeepCover
|
|
51
56
|
def config_changed(what)
|
52
57
|
case what
|
53
58
|
when :paths
|
54
|
-
warn "Changing DeepCover's paths after starting coverage is highly discouraged" if
|
59
|
+
warn "Changing DeepCover's paths after starting coverage is highly discouraged" if running?
|
55
60
|
@custom_requirer = nil
|
56
61
|
when :tracker_global
|
57
|
-
raise NotImplementedError, "Changing DeepCover's tracker global after starting coverage is not supported" if
|
62
|
+
raise NotImplementedError, "Changing DeepCover's tracker global after starting coverage is not supported" if running?
|
58
63
|
@coverage = nil
|
59
64
|
end
|
60
65
|
end
|
61
66
|
|
62
67
|
def reset
|
63
|
-
stop if
|
68
|
+
stop if running?
|
64
69
|
@coverage = @custom_requirer = @autoload_tracker = nil
|
65
70
|
config.reset
|
66
71
|
self
|
data/lib/deep_cover/basics.rb
CHANGED
@@ -59,7 +59,7 @@ module DeepCover
|
|
59
59
|
number_lines(lines, lineno: @lineno)
|
60
60
|
end
|
61
61
|
rescue Exception => e
|
62
|
-
output { "Can't run coverage: #{e.class
|
62
|
+
output { "Can't run coverage: #{e.class}: #{e}\n#{e.backtrace.join("\n")}" }
|
63
63
|
@failed = true
|
64
64
|
end
|
65
65
|
end
|
@@ -100,7 +100,7 @@ module DeepCover
|
|
100
100
|
execute_sample(covered_code)
|
101
101
|
# output { trace_counts } # Keep for low-level debugging purposes
|
102
102
|
rescue Exception => e
|
103
|
-
output { "Can't `execute_sample`:#{e.class
|
103
|
+
output { "Can't `execute_sample`:#{e.class}: #{e}\n#{e.backtrace.join("\n")}" }
|
104
104
|
@failed = true
|
105
105
|
end
|
106
106
|
|
@@ -20,8 +20,13 @@ module DeepCover
|
|
20
20
|
@root_path = @root_path.dirname
|
21
21
|
raise "Can't find Gemfile" unless @root_path.join('Gemfile').exist?
|
22
22
|
end
|
23
|
-
|
24
|
-
|
23
|
+
path = Pathname('~/test_deep_cover').expand_path
|
24
|
+
if path.exist?
|
25
|
+
@dest_root = path.join(@source_path.basename)
|
26
|
+
@dest_root.mkpath
|
27
|
+
else
|
28
|
+
@dest_root = Pathname.new(Dir.mktmpdir('deep_cover_test'))
|
29
|
+
end
|
25
30
|
|
26
31
|
gem_relative_path = @source_path.relative_path_from(@root_path)
|
27
32
|
@main_path = @dest_root.join(gem_relative_path)
|
@@ -107,6 +112,7 @@ module DeepCover
|
|
107
112
|
|
108
113
|
def patch_gemfile
|
109
114
|
gemfile = @dest_root.join('Gemfile')
|
115
|
+
require 'bundler'
|
110
116
|
deps = Bundler::Definition.build(gemfile, nil, nil).dependencies
|
111
117
|
|
112
118
|
return if deps.find { |e| e.name == 'deep-cover' }
|
@@ -137,12 +143,11 @@ module DeepCover
|
|
137
143
|
end
|
138
144
|
|
139
145
|
def cover
|
140
|
-
coverage = Coverage.new
|
146
|
+
coverage = Coverage.new(tracker_global: ::DeepCover.config.tracker_global)
|
141
147
|
each_dir_to_cover do |to_cover|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
coverage: coverage, root_path: @dest_root.to_s,
|
148
|
+
FileUtils.cp_r(to_cover, to_cover.sub_ext('_original'))
|
149
|
+
Tools.dump_covered_code(to_cover,
|
150
|
+
coverage: coverage,
|
146
151
|
dest_path: to_cover)
|
147
152
|
end
|
148
153
|
coverage.save(@dest_root.to_s)
|
@@ -150,7 +155,14 @@ module DeepCover
|
|
150
155
|
|
151
156
|
def process
|
152
157
|
Bundler.with_clean_env do
|
153
|
-
system("cd #{@main_path} && #{@options[:command]}")
|
158
|
+
system({'DISABLE_SPRING' => 'true'}, "cd #{@main_path} && #{@options[:command]}")
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def restore
|
163
|
+
each_dir_to_cover do |to_cover|
|
164
|
+
FileUtils.mv(to_cover, to_cover.sub_ext('_instrumented'))
|
165
|
+
FileUtils.mv(to_cover.sub_ext('_original'), to_cover)
|
154
166
|
end
|
155
167
|
end
|
156
168
|
|
@@ -174,6 +186,7 @@ module DeepCover
|
|
174
186
|
patch
|
175
187
|
bundle if @options[:bundle]
|
176
188
|
process
|
189
|
+
restore
|
177
190
|
end
|
178
191
|
report
|
179
192
|
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
require 'slop'
|
5
|
+
require_relative '../../deep-cover'
|
6
|
+
bootstrap
|
7
|
+
|
8
|
+
module CLI
|
9
|
+
module SlopExtension
|
10
|
+
attr_accessor :stopped
|
11
|
+
attr_reader :ignored
|
12
|
+
|
13
|
+
def try_process(*)
|
14
|
+
@ignored ||= 0
|
15
|
+
return if stopped
|
16
|
+
o = super
|
17
|
+
@ignored += 1 unless o
|
18
|
+
o
|
19
|
+
end
|
20
|
+
end
|
21
|
+
::Slop::Parser.prepend SlopExtension
|
22
|
+
|
23
|
+
module Runner
|
24
|
+
extend self
|
25
|
+
|
26
|
+
def show_version
|
27
|
+
require_relative '../version'
|
28
|
+
require 'parser'
|
29
|
+
puts "deep-cover v#{DeepCover::VERSION}; parser v#{Parser::VERSION}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def show_help
|
33
|
+
puts menu
|
34
|
+
end
|
35
|
+
|
36
|
+
class OptionParser < Struct.new(:delegate)
|
37
|
+
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissing
|
38
|
+
options = args.last
|
39
|
+
if options.is_a?(Hash) && options.has_key?(:default)
|
40
|
+
args[-2] += " [#{options[:default]}]"
|
41
|
+
end
|
42
|
+
delegate.public_send(method, *args, &block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse
|
47
|
+
Slop.parse do |o|
|
48
|
+
yield OptionParser.new(o)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def menu
|
53
|
+
@menu ||= parse do |o|
|
54
|
+
o.banner = ['usage: deep-cover [options] exec <command ...>',
|
55
|
+
' or deep-cover [options] [path/to/app/or/gem]',
|
56
|
+
].join("\n")
|
57
|
+
o.separator ''
|
58
|
+
o.string '-o', '--output', 'output folder', default: DeepCover.config.output
|
59
|
+
o.string '--reporter', 'reporter', default: DeepCover.config.reporter
|
60
|
+
o.bool '--open', 'open the output coverage', default: CLI_DEFAULTS[:open]
|
61
|
+
|
62
|
+
o.separator 'Coverage options'
|
63
|
+
@ignore_uncovered_map = OPTIONALLY_COVERED.map do |option|
|
64
|
+
default = DeepCover.config.ignore_uncovered.include?(option)
|
65
|
+
o.bool "--ignore-#{dasherize(option)}", '', default: default
|
66
|
+
[:"ignore_#{option}", option]
|
67
|
+
end.to_h
|
68
|
+
|
69
|
+
o.separator "\nWhen not using ’exec’:"
|
70
|
+
o.string '-c', '--command', 'command to run tests', default: CLI_DEFAULTS[:command]
|
71
|
+
o.bool '--bundle', 'run bundle before the tests', default: CLI_DEFAULTS[:bundle]
|
72
|
+
o.bool '--process', 'turn off to only redo the reporting', default: CLI_DEFAULTS[:process]
|
73
|
+
|
74
|
+
o.separator "\nFor testing purposes:"
|
75
|
+
o.bool '--profile', 'use profiler' unless RUBY_PLATFORM == 'java'
|
76
|
+
o.string '-e', '--expression', 'test ruby expression instead of a covering a path'
|
77
|
+
o.bool '-d', '--debug', 'enter debugging after cover'
|
78
|
+
|
79
|
+
o.separator "\nOther available commands:"
|
80
|
+
o.on('--version', 'print the version') do
|
81
|
+
show_version
|
82
|
+
exit
|
83
|
+
end
|
84
|
+
o.boolean('-h', '--help')
|
85
|
+
|
86
|
+
o.boolean('exec', '', help: false) do
|
87
|
+
o.parser.stopped = true if o.parser.ignored == 0
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def convert_options(options)
|
93
|
+
iu = options[:ignore_uncovered] = []
|
94
|
+
@ignore_uncovered_map.each do |cli_option, option|
|
95
|
+
iu << option if options.delete(cli_option)
|
96
|
+
end
|
97
|
+
options[:output] = false if ['false', 'f', ''].include?(options[:output])
|
98
|
+
options
|
99
|
+
end
|
100
|
+
|
101
|
+
def go
|
102
|
+
options = convert_options(menu.to_h)
|
103
|
+
if options[:help]
|
104
|
+
show_help
|
105
|
+
elsif options[:expression]
|
106
|
+
require_relative 'debugger'
|
107
|
+
Debugger.new(options[:expression], **options).show
|
108
|
+
elsif menu.parser.stopped
|
109
|
+
require_relative 'exec'
|
110
|
+
Exec.new(menu.arguments, **options).run
|
111
|
+
else
|
112
|
+
require_relative 'instrumented_clone_reporter'
|
113
|
+
path = menu.arguments.first || '.'
|
114
|
+
InstrumentedCloneReporter.new(path, **options).run
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
# Poor man's dasherize. 'an_example' => 'an-example'
|
121
|
+
def dasherize(string)
|
122
|
+
string.to_s.tr('_', '-')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|