deep-cover-core 0.6.4 → 0.7.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/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
|