deep-cover-core 0.6.4 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/deep_cover_core.gemspec +1 -0
- data/lib/deep_cover.rb +3 -20
- data/lib/deep_cover/auto_run.rb +10 -35
- data/lib/deep_cover/autoload_tracker.rb +9 -5
- data/lib/deep_cover/base.rb +85 -12
- data/lib/deep_cover/basics.rb +15 -3
- data/lib/deep_cover/config.rb +36 -2
- data/lib/deep_cover/config_setter.rb +1 -1
- data/lib/deep_cover/core_ext/exec_callbacks.rb +15 -9
- data/lib/deep_cover/core_ext/instruction_sequence_load_iseq.rb +1 -1
- data/lib/deep_cover/coverage.rb +51 -41
- data/lib/deep_cover/covered_code.rb +54 -17
- data/lib/deep_cover/custom_requirer.rb +10 -10
- data/lib/deep_cover/global_variables.rb +32 -0
- data/lib/deep_cover/load.rb +25 -9
- data/lib/deep_cover/node/base.rb +6 -6
- data/lib/deep_cover/node/begin.rb +1 -2
- data/lib/deep_cover/node/block.rb +5 -1
- data/lib/deep_cover/node/case.rb +1 -1
- data/lib/deep_cover/node/empty_body.rb +1 -5
- data/lib/deep_cover/node/if.rb +3 -4
- data/lib/deep_cover/node/loops.rb +1 -1
- data/lib/deep_cover/node/mixin/flow_accounting.rb +1 -8
- data/lib/deep_cover/node/mixin/has_child.rb +3 -12
- data/lib/deep_cover/node/mixin/has_child_handler.rb +2 -5
- data/lib/deep_cover/node/mixin/has_tracker.rb +3 -7
- data/lib/deep_cover/node/module.rb +20 -23
- data/lib/deep_cover/node/root.rb +1 -1
- data/lib/deep_cover/node/send.rb +4 -5
- data/lib/deep_cover/node/short_circuit.rb +1 -1
- data/lib/deep_cover/persistence.rb +100 -0
- data/lib/deep_cover/problem_with_diagnostic.rb +2 -2
- data/lib/deep_cover/setup/clone_mode_entry_template.rb +66 -0
- data/lib/deep_cover/setup/deep_cover_auto_run.rb +21 -0
- data/lib/deep_cover/setup/deep_cover_config.rb +19 -0
- data/lib/deep_cover/setup/deep_cover_without_config.rb +15 -0
- data/lib/deep_cover/tools.rb +0 -2
- data/lib/deep_cover/tools/after_tests.rb +33 -0
- data/lib/deep_cover/tools/looks_like_rails_project.rb +13 -0
- data/lib/deep_cover/tools/our_coverage.rb +1 -1
- data/lib/deep_cover/tools/scan_match_datas.rb +1 -1
- data/lib/deep_cover/version.rb +4 -2
- metadata +24 -7
- data/lib/deep_cover/coverage/persistence.rb +0 -84
- data/lib/deep_cover/tracker_bucket.rb +0 -50
- data/lib/deep_cover/tracker_hits_per_path.rb +0 -35
- data/lib/deep_cover/tracker_storage.rb +0 -76
- data/lib/deep_cover/tracker_storage_per_path.rb +0 -34
data/lib/deep_cover/coverage.rb
CHANGED
@@ -9,12 +9,8 @@ module DeepCover
|
|
9
9
|
class Coverage
|
10
10
|
include Enumerable
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
def initialize(**options)
|
15
|
-
@covered_code_index = {}
|
16
|
-
@options = options
|
17
|
-
@tracker_storage_per_path = TrackerStoragePerPath.new(TrackerBucket[tracker_global])
|
12
|
+
def initialize
|
13
|
+
@covered_code_per_path = {}
|
18
14
|
end
|
19
15
|
|
20
16
|
def covered_codes
|
@@ -26,15 +22,13 @@ module DeepCover
|
|
26
22
|
end
|
27
23
|
|
28
24
|
def covered_code?(path)
|
29
|
-
@
|
25
|
+
@covered_code_per_path.include?(path)
|
30
26
|
end
|
31
27
|
|
32
28
|
def covered_code(path, **options)
|
33
29
|
raise 'path must be an absolute path' unless Pathname.new(path).absolute?
|
34
|
-
@
|
35
|
-
|
36
|
-
**options,
|
37
|
-
**@options)
|
30
|
+
@covered_code_per_path[path] ||= CoveredCode.new(path: path,
|
31
|
+
**options)
|
38
32
|
end
|
39
33
|
|
40
34
|
def covered_code_or_warn(path, **options)
|
@@ -51,7 +45,7 @@ module DeepCover
|
|
51
45
|
|
52
46
|
def each
|
53
47
|
return to_enum unless block_given?
|
54
|
-
@
|
48
|
+
@covered_code_per_path.each_key do |path|
|
55
49
|
begin
|
56
50
|
cov_code = covered_code(path)
|
57
51
|
rescue Parser::SyntaxError
|
@@ -62,8 +56,36 @@ module DeepCover
|
|
62
56
|
self
|
63
57
|
end
|
64
58
|
|
65
|
-
|
66
|
-
|
59
|
+
# If a file wasn't required, it won't be in the trackers. This adds those mossing files
|
60
|
+
def add_missing_covered_codes
|
61
|
+
top_level_path = DeepCover.config.paths.detect do |path|
|
62
|
+
next unless path.is_a?(String)
|
63
|
+
path = File.expand_path(path)
|
64
|
+
File.dirname(path) == path
|
65
|
+
end
|
66
|
+
if top_level_path
|
67
|
+
# One of the paths is a root path.
|
68
|
+
# Either a mistake, or the user wanted to check everything that gets loaded. Either way,
|
69
|
+
# we will probably hang from searching the files to load. (and then loading them, as there
|
70
|
+
# can be lots of ruby files) We prefer to warn the user and not do anything.
|
71
|
+
suggestion = "#{top_level_path}/**/*.rb"
|
72
|
+
warn ["Because the `paths` configured in DeepCover include #{top_level_path.inspect}, which is a root of your fs, ",
|
73
|
+
'DeepCover will not attempt to find Ruby files that were not required. Your coverage report will not include ',
|
74
|
+
'files that were not instrumented. This is to avoid extremely long wait times. If you actually want this to ',
|
75
|
+
"happen, then replace the specified `paths` with #{suggestion.inspect}.",
|
76
|
+
].join
|
77
|
+
return
|
78
|
+
end
|
79
|
+
|
80
|
+
DeepCover.all_tracked_file_paths.each do |path|
|
81
|
+
covered_code(path, tracker_hits: :zeroes)
|
82
|
+
end
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def report(reporter: DEFAULTS[:reporter], **options)
|
87
|
+
add_missing_covered_codes
|
88
|
+
case reporter.to_sym
|
67
89
|
when :html
|
68
90
|
msg = if (output = options.fetch(:output, DEFAULTS[:output]))
|
69
91
|
Reporter::HTML.report(self, **options)
|
@@ -85,41 +107,29 @@ module DeepCover
|
|
85
107
|
end
|
86
108
|
end
|
87
109
|
|
88
|
-
def self.load(
|
89
|
-
Persistence.new(
|
90
|
-
|
110
|
+
def self.load(cache_directory = DeepCover.config.cache_directory)
|
111
|
+
tracker_hits_per_path = Persistence.new(cache_directory).load_trackers
|
112
|
+
coverage = Coverage.new
|
91
113
|
|
92
|
-
|
93
|
-
|
94
|
-
|
114
|
+
tracker_hits_per_path.each do |path, tracker_hits|
|
115
|
+
coverage.covered_code(path, tracker_hits: tracker_hits)
|
116
|
+
end
|
95
117
|
|
96
|
-
|
97
|
-
Persistence.new(dest_path, dirname).save(self)
|
98
|
-
self
|
118
|
+
coverage
|
99
119
|
end
|
100
120
|
|
101
|
-
def save_trackers
|
102
|
-
|
103
|
-
|
104
|
-
|
121
|
+
def save_trackers
|
122
|
+
tracker_hits_per_path = @covered_code_per_path.map do |path, covered_code|
|
123
|
+
[path, covered_code.tracker_hits]
|
124
|
+
end
|
125
|
+
tracker_hits_per_path = tracker_hits_per_path.to_h
|
105
126
|
|
106
|
-
|
107
|
-
|
127
|
+
DeepCover.persistence.save_trackers(tracker_hits_per_path)
|
128
|
+
self
|
108
129
|
end
|
109
130
|
|
110
131
|
def analysis(**options)
|
111
|
-
Analysis.new(covered_codes, options)
|
112
|
-
end
|
113
|
-
|
114
|
-
private
|
115
|
-
|
116
|
-
def marshal_dump
|
117
|
-
{options: @options, tracker_storage_per_path: @tracker_storage_per_path}
|
118
|
-
end
|
119
|
-
|
120
|
-
def marshal_load(options:, tracker_storage_per_path:)
|
121
|
-
initialize(**options)
|
122
|
-
@tracker_storage_per_path = tracker_storage_per_path
|
132
|
+
Analysis.new(covered_codes, **options)
|
123
133
|
end
|
124
134
|
end
|
125
135
|
end
|
@@ -5,25 +5,28 @@ module DeepCover
|
|
5
5
|
load_parser
|
6
6
|
|
7
7
|
class CoveredCode
|
8
|
-
attr_accessor :
|
8
|
+
attr_accessor :buffer, :local_var, :path
|
9
9
|
|
10
10
|
def initialize(
|
11
11
|
path: nil,
|
12
12
|
source: nil,
|
13
13
|
lineno: 1,
|
14
|
-
|
15
|
-
|
16
|
-
local_var: '_temp'
|
14
|
+
local_var: '_temp',
|
15
|
+
tracker_hits: nil
|
17
16
|
)
|
18
17
|
raise 'Must provide either path or source' unless path || source
|
19
18
|
|
20
19
|
@path = path &&= Pathname(path)
|
21
20
|
@buffer = Parser::Source::Buffer.new('', lineno)
|
22
21
|
@buffer.source = source || path.read
|
23
|
-
@
|
24
|
-
@tracker_storage = tracker_storage
|
22
|
+
@index = nil # Set in #instrument_source
|
25
23
|
@local_var = local_var
|
26
|
-
@covered_source =
|
24
|
+
@covered_source = nil # Set in #covered_source
|
25
|
+
@tracker_hits = tracker_hits # Loaded from global in #tracker_hits, or when received right away when loading data
|
26
|
+
@nb_allocated_trackers = 0
|
27
|
+
# We parse the code now so that problems happen early
|
28
|
+
covered_ast
|
29
|
+
@tracker_hits = Array.new(@nb_allocated_trackers, 0) if @tracker_hits == :zeroes
|
27
30
|
end
|
28
31
|
|
29
32
|
def lineno
|
@@ -40,7 +43,7 @@ module DeepCover
|
|
40
43
|
end
|
41
44
|
|
42
45
|
def execute_code(binding: DeepCover::GLOBAL_BINDING.dup)
|
43
|
-
eval(
|
46
|
+
eval(covered_source, binding, (@path || '<raw_code>').to_s, lineno) # rubocop:disable Security/Eval
|
44
47
|
self
|
45
48
|
end
|
46
49
|
|
@@ -50,10 +53,6 @@ module DeepCover
|
|
50
53
|
end
|
51
54
|
end
|
52
55
|
|
53
|
-
def cover
|
54
|
-
global[nb] ||= Array.new(@tracker_count, 0)
|
55
|
-
end
|
56
|
-
|
57
56
|
def line_coverage(**options)
|
58
57
|
Analyser::PerLine.new(self, **options).results
|
59
58
|
end
|
@@ -62,10 +61,6 @@ module DeepCover
|
|
62
61
|
Analyser::PerChar.new(self, **options).results
|
63
62
|
end
|
64
63
|
|
65
|
-
def tracker_hits(tracker_id)
|
66
|
-
cover.fetch(tracker_id)
|
67
|
-
end
|
68
|
-
|
69
64
|
def covered_ast
|
70
65
|
root.main
|
71
66
|
end
|
@@ -86,9 +81,45 @@ module DeepCover
|
|
86
81
|
covered_ast.each_node(*args, &block)
|
87
82
|
end
|
88
83
|
|
84
|
+
def setup_tracking_source
|
85
|
+
src = "(#{DeepCover.config.tracker_global}||={})[#{@index}]||=Array.new(#{@nb_allocated_trackers},0)"
|
86
|
+
src += ";(#{DeepCover.config.tracker_global}_p||={})[#{@index}]=#{path.to_s.inspect}" if path
|
87
|
+
src
|
88
|
+
end
|
89
|
+
|
90
|
+
def increment_tracker_source(tracker_id)
|
91
|
+
"#{DeepCover.config.tracker_global}[#{@index}][#{tracker_id}]+=1"
|
92
|
+
end
|
93
|
+
|
94
|
+
def allocate_trackers(nb_needed)
|
95
|
+
return @nb_allocated_trackers...@nb_allocated_trackers if nb_needed == 0
|
96
|
+
prev = @nb_allocated_trackers
|
97
|
+
@nb_allocated_trackers += nb_needed
|
98
|
+
prev...@nb_allocated_trackers
|
99
|
+
end
|
100
|
+
|
101
|
+
def tracker_hits
|
102
|
+
return @tracker_hits if @tracker_hits
|
103
|
+
global_trackers = DeepCover::GlobalVariables.trackers[@index]
|
104
|
+
|
105
|
+
return unless global_trackers
|
106
|
+
|
107
|
+
if global_trackers.size != @nb_allocated_trackers
|
108
|
+
raise "Cannot sync path: #{path.inspect}, global[#{@index}] is of size #{global_trackers.size} instead of expected #{@nb_allocated_trackers}"
|
109
|
+
end
|
110
|
+
|
111
|
+
@tracker_hits = global_trackers
|
112
|
+
end
|
113
|
+
|
114
|
+
def covered_source
|
115
|
+
@covered_source ||= instrument_source
|
116
|
+
end
|
117
|
+
|
89
118
|
def instrument_source
|
119
|
+
@index ||= self.class.next_global_index
|
120
|
+
|
90
121
|
rewriter = Parser::Source::TreeRewriter.new(@buffer)
|
91
|
-
covered_ast.each_node
|
122
|
+
covered_ast.each_node do |node|
|
92
123
|
node.rewriting_rules.each do |range, rule|
|
93
124
|
prefix, _node, suffix = rule.partition('%{node}')
|
94
125
|
prefix = yield prefix, node, range.begin, :prefix if block_given? && !prefix.empty?
|
@@ -111,6 +142,7 @@ module DeepCover
|
|
111
142
|
|
112
143
|
def freeze
|
113
144
|
unless frozen? # Guard against reentrance
|
145
|
+
tracker_hits
|
114
146
|
super
|
115
147
|
root.each_node(&:freeze)
|
116
148
|
end
|
@@ -133,6 +165,11 @@ module DeepCover
|
|
133
165
|
nil
|
134
166
|
end
|
135
167
|
|
168
|
+
def self.next_global_index
|
169
|
+
@last_allocated_global_index ||= -1
|
170
|
+
@last_allocated_global_index += 1
|
171
|
+
end
|
172
|
+
|
136
173
|
private
|
137
174
|
|
138
175
|
def parser
|
@@ -22,12 +22,12 @@ module DeepCover
|
|
22
22
|
#
|
23
23
|
# An absolute path is returned directly if it exists, otherwise nil is returned
|
24
24
|
# without searching anywhere else.
|
25
|
-
def resolve_path(path,
|
26
|
-
if
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
def resolve_path(path, try_extensions: true)
|
26
|
+
extensions_to_try = if try_extensions && !REQUIRABLE_EXTENSIONS.include?(File.extname(path))
|
27
|
+
REQUIRABLE_EXTENSION_KEYS
|
28
|
+
else
|
29
|
+
['']
|
30
|
+
end
|
31
31
|
|
32
32
|
abs_path = File.absolute_path(path)
|
33
33
|
path = abs_path if path.start_with?('./', '../')
|
@@ -89,8 +89,8 @@ module DeepCover
|
|
89
89
|
return yield(:not_found) unless found_path
|
90
90
|
|
91
91
|
@paths_being_required.add(found_path)
|
92
|
-
return yield(:not_in_covered_paths) unless DeepCover.
|
93
|
-
return yield(:not_supported) if
|
92
|
+
return yield(:not_in_covered_paths) unless DeepCover.tracked_file_path?(found_path)
|
93
|
+
return yield(:not_supported) if REQUIRABLE_EXTENSIONS[File.extname(found_path)] == :native_extension
|
94
94
|
return yield(:skipped) if filter && filter.call(found_path)
|
95
95
|
|
96
96
|
cover_and_execute(found_path) { |reason| return yield(reason) }
|
@@ -111,7 +111,7 @@ module DeepCover
|
|
111
111
|
def load(path) # &fallback_block
|
112
112
|
path = path.to_s
|
113
113
|
|
114
|
-
found_path = resolve_path(path,
|
114
|
+
found_path = resolve_path(path, try_extensions: false)
|
115
115
|
|
116
116
|
if found_path.nil?
|
117
117
|
# #load has a final fallback of always trying relative to current work directory
|
@@ -120,7 +120,7 @@ module DeepCover
|
|
120
120
|
end
|
121
121
|
|
122
122
|
return yield(:not_found) unless found_path
|
123
|
-
return yield(:not_in_covered_paths) unless DeepCover.
|
123
|
+
return yield(:not_in_covered_paths) unless DeepCover.tracked_file_path?(found_path)
|
124
124
|
|
125
125
|
cover_and_execute(found_path) { |reason| return yield(reason) }
|
126
126
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is used by projects cloned with clone mode. As such, special care must be taken to
|
4
|
+
# be compatible with any projects.
|
5
|
+
# THERE MUST NOT BE ANY USE/REQUIRE OF DEPENDENCIES OF DeepCover HERE
|
6
|
+
# See deep-cover/core_gem/lib/deep_cover/setup/clone_mode_entry_template.rb for explanation of
|
7
|
+
# clone mode and of this top_level_module stuff.
|
8
|
+
top_level_module = Thread.current['_deep_cover_top_level_module'] || Object # rubocop:disable Lint/UselessAssignment
|
9
|
+
|
10
|
+
module top_level_module::DeepCover # rubocop:disable Naming/ClassAndModuleCamelCase
|
11
|
+
module GlobalVariables
|
12
|
+
def self.trackers(global_name = nil)
|
13
|
+
@trackers ||= {}
|
14
|
+
global_name ||= DeepCover.config.tracker_global
|
15
|
+
@trackers[global_name] ||= eval("#{global_name} ||= {}") # rubocop:disable Security/Eval
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.path_per_index(global_name = nil)
|
19
|
+
@path_per_index ||= {}
|
20
|
+
global_name ||= DeepCover.config.tracker_global
|
21
|
+
@path_per_index[global_name] ||= eval("#{global_name}_p ||= {}") # rubocop:disable Security/Eval
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.tracker_hits_per_path(global_name = nil)
|
25
|
+
cur_trackers = self.trackers(global_name)
|
26
|
+
hits_per_path = path_per_index(global_name).map do |index, path|
|
27
|
+
[path, cur_trackers[index]]
|
28
|
+
end
|
29
|
+
hits_per_path.to_h
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/deep_cover/load.rb
CHANGED
@@ -4,9 +4,8 @@ module DeepCover
|
|
4
4
|
module Load
|
5
5
|
AUTOLOAD = %i[analyser autoload_tracker auto_run config
|
6
6
|
coverage covered_code custom_requirer
|
7
|
-
|
8
|
-
|
9
|
-
problem_with_diagnostic reporter tracker_bucket
|
7
|
+
flag_comment_associator global_variables memoize module_override node
|
8
|
+
persistence problem_with_diagnostic reporter
|
10
9
|
]
|
11
10
|
|
12
11
|
def load_absolute_basics
|
@@ -18,10 +17,27 @@ module DeepCover
|
|
18
17
|
DeepCover.autoload(Tools::Camelize.camelize(module_name), "#{__dir__}/#{module_name}")
|
19
18
|
end
|
20
19
|
DeepCover.autoload :VERSION, "#{__dir__}/version"
|
21
|
-
|
22
|
-
Object.autoload :Terminal, 'terminal-table'
|
23
|
-
Object.autoload :YAML, 'yaml'
|
20
|
+
|
24
21
|
Object.autoload :Forwardable, 'forwardable'
|
22
|
+
Object.autoload :YAML, 'yaml'
|
23
|
+
|
24
|
+
# In ruby 2.2 and less, autoload doesn't work for gems which are not already on the `$LOAD_PATH`.
|
25
|
+
# The fix is to just require right away for those rubies
|
26
|
+
#
|
27
|
+
# Low-level: autoload not working for gems not on the `$LOAD_PATH` is because those rubies don't
|
28
|
+
# call the regular `#require` when triggering an autoload, and the gem system monkey-patches `#require`
|
29
|
+
# so that when a file is not found in the `$LOAD_PATH`, but can be found in an existing gem, that gem's
|
30
|
+
# path is added to the `$LOAD_PATH`
|
31
|
+
{JSON: 'json',
|
32
|
+
Term: 'term/ansicolor',
|
33
|
+
Terminal: 'terminal-table',
|
34
|
+
}.each do |const, require_path|
|
35
|
+
if RUBY_VERSION < '2.3'
|
36
|
+
require require_path
|
37
|
+
else
|
38
|
+
Object.autoload const, require_path
|
39
|
+
end
|
40
|
+
end
|
25
41
|
end
|
26
42
|
|
27
43
|
def bootstrap
|
@@ -35,7 +51,7 @@ module DeepCover
|
|
35
51
|
def load_parser
|
36
52
|
@parser_loaded ||= false # Avoid warning
|
37
53
|
return if @parser_loaded
|
38
|
-
silence_warnings do
|
54
|
+
Tools.silence_warnings do
|
39
55
|
require 'parser'
|
40
56
|
require 'parser/current'
|
41
57
|
end
|
@@ -44,8 +60,8 @@ module DeepCover
|
|
44
60
|
end
|
45
61
|
|
46
62
|
def load_pry
|
47
|
-
silence_warnings do # Avoid "WARN: Unresolved specs during Gem::Specification.reset"
|
48
|
-
require 'pry'
|
63
|
+
Tools.silence_warnings do # Avoid "WARN: Unresolved specs during Gem::Specification.reset"
|
64
|
+
require 'pry' # after `pry` calls `Gem.refresh`
|
49
65
|
end
|
50
66
|
end
|
51
67
|
|
data/lib/deep_cover/node/base.rb
CHANGED
@@ -114,17 +114,17 @@ module DeepCover
|
|
114
114
|
end
|
115
115
|
|
116
116
|
def type
|
117
|
-
return base_node.type if base_node
|
117
|
+
return base_node.type if base_node
|
118
118
|
self.class.name.split('::').last.to_sym
|
119
119
|
end
|
120
120
|
|
121
|
-
|
122
|
-
|
123
|
-
|
121
|
+
# Yields its children and itself
|
122
|
+
def each_node(&block)
|
123
|
+
return to_enum :each_node unless block_given?
|
124
124
|
children_nodes.each do |child|
|
125
|
-
child.each_node(
|
125
|
+
child.each_node(&block)
|
126
126
|
end
|
127
|
-
yield self
|
127
|
+
yield self
|
128
128
|
self
|
129
129
|
end
|
130
130
|
|
@@ -38,8 +38,12 @@ module DeepCover
|
|
38
38
|
is_statement: true
|
39
39
|
executed_loc_keys # none
|
40
40
|
|
41
|
+
def execution_count
|
42
|
+
call.execution_count
|
43
|
+
end
|
44
|
+
|
41
45
|
def children_nodes_in_flow_order
|
42
|
-
[call
|
46
|
+
[call] # Similarly to a def, the body (and Args) are actually not part of the flow of this node...
|
43
47
|
end
|
44
48
|
|
45
49
|
alias_method :rewrite_for_completion, :rewrite
|