deep-cover-core 0.6.3.pre
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/.rspec +4 -0
- data/.rspec_all +3 -0
- data/.rubocop.yml +1 -0
- data/Gemfile +11 -0
- data/deep_cover_core.gemspec +46 -0
- data/lib/deep-cover.rb +3 -0
- data/lib/deep_cover/analyser/base.rb +104 -0
- data/lib/deep_cover/analyser/branch.rb +41 -0
- data/lib/deep_cover/analyser/covered_code_source.rb +21 -0
- data/lib/deep_cover/analyser/function.rb +14 -0
- data/lib/deep_cover/analyser/node.rb +54 -0
- data/lib/deep_cover/analyser/per_char.rb +38 -0
- data/lib/deep_cover/analyser/per_line.rb +41 -0
- data/lib/deep_cover/analyser/ruby25_like_branch.rb +211 -0
- data/lib/deep_cover/analyser/statement.rb +33 -0
- data/lib/deep_cover/analyser/stats.rb +54 -0
- data/lib/deep_cover/analyser/subset.rb +27 -0
- data/lib/deep_cover/analyser.rb +23 -0
- data/lib/deep_cover/auto_run.rb +71 -0
- data/lib/deep_cover/autoload_tracker.rb +215 -0
- data/lib/deep_cover/backports.rb +22 -0
- data/lib/deep_cover/base.rb +117 -0
- data/lib/deep_cover/basics.rb +22 -0
- data/lib/deep_cover/builtin_takeover.rb +10 -0
- data/lib/deep_cover/config.rb +104 -0
- data/lib/deep_cover/config_setter.rb +33 -0
- data/lib/deep_cover/core_ext/autoload_overrides.rb +112 -0
- data/lib/deep_cover/core_ext/coverage_replacement.rb +61 -0
- data/lib/deep_cover/core_ext/exec_callbacks.rb +27 -0
- data/lib/deep_cover/core_ext/instruction_sequence_load_iseq.rb +32 -0
- data/lib/deep_cover/core_ext/load_overrides.rb +19 -0
- data/lib/deep_cover/core_ext/require_overrides.rb +28 -0
- data/lib/deep_cover/coverage/analysis.rb +42 -0
- data/lib/deep_cover/coverage/persistence.rb +84 -0
- data/lib/deep_cover/coverage.rb +125 -0
- data/lib/deep_cover/covered_code.rb +145 -0
- data/lib/deep_cover/custom_requirer.rb +187 -0
- data/lib/deep_cover/flag_comment_associator.rb +68 -0
- data/lib/deep_cover/load.rb +66 -0
- data/lib/deep_cover/memoize.rb +48 -0
- data/lib/deep_cover/module_override.rb +39 -0
- data/lib/deep_cover/node/arguments.rb +51 -0
- data/lib/deep_cover/node/assignments.rb +273 -0
- data/lib/deep_cover/node/base.rb +155 -0
- data/lib/deep_cover/node/begin.rb +27 -0
- data/lib/deep_cover/node/block.rb +61 -0
- data/lib/deep_cover/node/branch.rb +32 -0
- data/lib/deep_cover/node/case.rb +113 -0
- data/lib/deep_cover/node/collections.rb +31 -0
- data/lib/deep_cover/node/const.rb +12 -0
- data/lib/deep_cover/node/def.rb +40 -0
- data/lib/deep_cover/node/empty_body.rb +32 -0
- data/lib/deep_cover/node/exceptions.rb +79 -0
- data/lib/deep_cover/node/if.rb +73 -0
- data/lib/deep_cover/node/keywords.rb +86 -0
- data/lib/deep_cover/node/literals.rb +100 -0
- data/lib/deep_cover/node/loops.rb +74 -0
- data/lib/deep_cover/node/mixin/can_augment_children.rb +65 -0
- data/lib/deep_cover/node/mixin/check_completion.rb +18 -0
- data/lib/deep_cover/node/mixin/child_can_be_empty.rb +27 -0
- data/lib/deep_cover/node/mixin/executed_after_children.rb +15 -0
- data/lib/deep_cover/node/mixin/execution_location.rb +66 -0
- data/lib/deep_cover/node/mixin/filters.rb +47 -0
- data/lib/deep_cover/node/mixin/flow_accounting.rb +71 -0
- data/lib/deep_cover/node/mixin/has_child.rb +145 -0
- data/lib/deep_cover/node/mixin/has_child_handler.rb +75 -0
- data/lib/deep_cover/node/mixin/has_tracker.rb +46 -0
- data/lib/deep_cover/node/mixin/is_statement.rb +20 -0
- data/lib/deep_cover/node/mixin/rewriting.rb +35 -0
- data/lib/deep_cover/node/mixin/wrapper.rb +15 -0
- data/lib/deep_cover/node/module.rb +66 -0
- data/lib/deep_cover/node/root.rb +20 -0
- data/lib/deep_cover/node/send.rb +161 -0
- data/lib/deep_cover/node/short_circuit.rb +42 -0
- data/lib/deep_cover/node/splat.rb +15 -0
- data/lib/deep_cover/node/variables.rb +16 -0
- data/lib/deep_cover/node.rb +23 -0
- data/lib/deep_cover/parser_ext/range.rb +21 -0
- data/lib/deep_cover/problem_with_diagnostic.rb +63 -0
- data/lib/deep_cover/reporter/base.rb +68 -0
- data/lib/deep_cover/reporter/html/base.rb +14 -0
- data/lib/deep_cover/reporter/html/index.rb +59 -0
- data/lib/deep_cover/reporter/html/site.rb +68 -0
- data/lib/deep_cover/reporter/html/source.rb +136 -0
- data/lib/deep_cover/reporter/html/template/assets/32px.png +0 -0
- data/lib/deep_cover/reporter/html/template/assets/40px.png +0 -0
- data/lib/deep_cover/reporter/html/template/assets/deep_cover.css +291 -0
- data/lib/deep_cover/reporter/html/template/assets/deep_cover.css.sass +336 -0
- data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.js +4 -0
- data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.map +1 -0
- data/lib/deep_cover/reporter/html/template/assets/jstree.css +1108 -0
- data/lib/deep_cover/reporter/html/template/assets/jstree.js +8424 -0
- data/lib/deep_cover/reporter/html/template/assets/jstreetable.js +1069 -0
- data/lib/deep_cover/reporter/html/template/assets/throbber.gif +0 -0
- data/lib/deep_cover/reporter/html/template/index.html.erb +75 -0
- data/lib/deep_cover/reporter/html/template/source.html.erb +35 -0
- data/lib/deep_cover/reporter/html.rb +15 -0
- data/lib/deep_cover/reporter/istanbul.rb +184 -0
- data/lib/deep_cover/reporter/text.rb +58 -0
- data/lib/deep_cover/reporter/tree/util.rb +86 -0
- data/lib/deep_cover/reporter.rb +10 -0
- data/lib/deep_cover/tools/blank.rb +25 -0
- data/lib/deep_cover/tools/builtin_coverage.rb +55 -0
- data/lib/deep_cover/tools/camelize.rb +13 -0
- data/lib/deep_cover/tools/content_tag.rb +11 -0
- data/lib/deep_cover/tools/covered.rb +9 -0
- data/lib/deep_cover/tools/execute_sample.rb +34 -0
- data/lib/deep_cover/tools/format.rb +18 -0
- data/lib/deep_cover/tools/format_char_cover.rb +19 -0
- data/lib/deep_cover/tools/format_generated_code.rb +27 -0
- data/lib/deep_cover/tools/indent_string.rb +26 -0
- data/lib/deep_cover/tools/merge.rb +16 -0
- data/lib/deep_cover/tools/number_lines.rb +22 -0
- data/lib/deep_cover/tools/our_coverage.rb +11 -0
- data/lib/deep_cover/tools/profiling.rb +68 -0
- data/lib/deep_cover/tools/render_template.rb +13 -0
- data/lib/deep_cover/tools/require_relative_dir.rb +12 -0
- data/lib/deep_cover/tools/scan_match_datas.rb +10 -0
- data/lib/deep_cover/tools/silence_warnings.rb +18 -0
- data/lib/deep_cover/tools/slice.rb +9 -0
- data/lib/deep_cover/tools/strip_heredoc.rb +18 -0
- data/lib/deep_cover/tools/truncate_backtrace.rb +32 -0
- data/lib/deep_cover/tools.rb +22 -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 +5 -0
- data/lib/deep_cover.rb +22 -0
- metadata +329 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Node::Mixin
|
5
|
+
module Filters
|
6
|
+
module ClassMethods
|
7
|
+
def filter_to_method_name(kind)
|
8
|
+
:"is_#{kind}?"
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_filter(name, &block)
|
12
|
+
Filters.define_method(filter_to_method_name(name), &block)
|
13
|
+
OPTIONALLY_COVERED << name
|
14
|
+
end
|
15
|
+
|
16
|
+
def unique_filter
|
17
|
+
(1..Float::INFINITY).each do |i|
|
18
|
+
name = :"custom_filter_#{i}"
|
19
|
+
return name unless Filters.method_defined?(filter_to_method_name(name))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
RAISING_MESSAGES = %i[raise exit].freeze
|
25
|
+
def is_raise?
|
26
|
+
is_a?(Node::Send) && RAISING_MESSAGES.include?(message) && receiver == nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def is_warn?
|
30
|
+
is_a?(Node::Send) && message == :warn
|
31
|
+
end
|
32
|
+
|
33
|
+
def is_default_argument?
|
34
|
+
parent.is_a?(Node::Optarg) && simple_literal?
|
35
|
+
end
|
36
|
+
|
37
|
+
def is_case_implicit_else?
|
38
|
+
is_a?(Node::EmptyBody) && parent.is_a?(Node::Case) && !parent.has_else?
|
39
|
+
end
|
40
|
+
|
41
|
+
def is_trivial_if?
|
42
|
+
# Supports only node being a branch or the fork itself
|
43
|
+
parent.is_a?(Node::If) && parent.condition.simple_literal?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Node::Mixin
|
5
|
+
module FlowAccounting
|
6
|
+
def self.included(base)
|
7
|
+
base.has_child_handler('%{name}_flow_entry_count')
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns true iff it is executable and if was successfully executed
|
11
|
+
def was_executed?
|
12
|
+
# There is a rare case of non executable nodes that have important data in flow_entry_count / flow_completion_count,
|
13
|
+
# like `if cond; end`, so make sure it's actually executable first...
|
14
|
+
executable? && execution_count > 0
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the control flow entered the node.
|
18
|
+
# The control flow can then either complete normally or be interrupted
|
19
|
+
#
|
20
|
+
# Implementation: This is always the responsibility of the parent; Nodes should not override.
|
21
|
+
def flow_entry_count
|
22
|
+
parent.child_flow_entry_count(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the number of times it changed the usual control flow (e.g. raised, returned, ...)
|
26
|
+
# Implementation: This is always deduced; Nodes should not override.
|
27
|
+
def flow_interrupt_count
|
28
|
+
flow_entry_count - flow_completion_count
|
29
|
+
end
|
30
|
+
|
31
|
+
### These are refined by subclasses
|
32
|
+
|
33
|
+
# Returns true iff it is executable. Keywords like `end` are not executable, but literals like `42` are executable.
|
34
|
+
def executable?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns number of times the node itself was "executed". Definition of executed depends on the node.
|
39
|
+
# For now at least, don't return `nil`, instead return `false` in `executable?`
|
40
|
+
def execution_count
|
41
|
+
flow_entry_count
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the number of times the control flow succesfully left the node.
|
45
|
+
# This is the responsability of the child Node, never of the parent.
|
46
|
+
# Must be refined if the child node may have an impact on control flow (raising, branching, ...)
|
47
|
+
def flow_completion_count
|
48
|
+
last = children_nodes_in_flow_order.last
|
49
|
+
return last.flow_completion_count if last
|
50
|
+
flow_entry_count
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the number of time the control flow entered this child_node.
|
54
|
+
# This is the responsability of the Node, not of the child.
|
55
|
+
# Must be refined if the parent node may have an impact on control flow (raising, branching, ...)
|
56
|
+
def child_flow_entry_count(child, _name = nil)
|
57
|
+
prev = child.previous_sibling
|
58
|
+
if prev
|
59
|
+
prev.flow_completion_count
|
60
|
+
else
|
61
|
+
flow_entry_count
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns the counts in a hash
|
66
|
+
def counts
|
67
|
+
{flow_entry: flow_entry_count, flow_completion: flow_completion_count, execution: execution_count}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Node::Mixin
|
5
|
+
module HasChild
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
CHILDREN = {}.freeze
|
10
|
+
CHILDREN_TYPES = {}.freeze
|
11
|
+
|
12
|
+
def initialize(*)
|
13
|
+
super
|
14
|
+
self.validate_children_types(children)
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate_children_types(nodes)
|
18
|
+
mismatches = self.class.check_children_types(nodes)
|
19
|
+
unless mismatches.empty?
|
20
|
+
raise TypeError, "Invalid children types for #{self.class}(type: #{self.type}): #{mismatches}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def child_index_to_name(index)
|
25
|
+
self.class.child_index_to_name(index, children.size)
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
def has_child(rest_: false, refine_: false, **args)
|
30
|
+
raise "Needs exactly one custom named argument, got #{args.size}" if args.size != 1
|
31
|
+
name, types = args.first
|
32
|
+
raise TypeError, "Expect a Symbol for name, got a #{name.class} (#{name.inspect})" unless name.is_a?(Symbol)
|
33
|
+
update_children_const(name, rest: rest_) unless refine_
|
34
|
+
define_accessor(name) unless refine_
|
35
|
+
add_runtime_check(name, types)
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_extra_children(**args)
|
40
|
+
has_child(**args, rest_: true)
|
41
|
+
end
|
42
|
+
|
43
|
+
def refine_child(child_name = nil, **args)
|
44
|
+
if child_name
|
45
|
+
args = {child_name => self::CHILDREN_TYPES.fetch(child_name), **args}
|
46
|
+
end
|
47
|
+
has_child(**args, refine_: true)
|
48
|
+
end
|
49
|
+
|
50
|
+
def child_index_to_name(index, nb_children)
|
51
|
+
self::CHILDREN.each do |name, i|
|
52
|
+
return name if i == index || (i == index - nb_children) ||
|
53
|
+
(i.is_a?(Range) && i.begin <= index && i.end + nb_children >= index)
|
54
|
+
end
|
55
|
+
raise IndexError, "index #{index} does not correspond to any child of #{self}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def check_children_types(nodes)
|
59
|
+
types = expected_types(nodes)
|
60
|
+
nodes_mismatches(nodes, types)
|
61
|
+
end
|
62
|
+
|
63
|
+
def min_children
|
64
|
+
self::CHILDREN.values.grep(Integer).size
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def expected_types(nodes)
|
70
|
+
self::CHILDREN.flat_map do |name, i|
|
71
|
+
type = self::CHILDREN_TYPES[name]
|
72
|
+
Array.new(nodes.values_at(i).size, type)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def nodes_mismatches(nodes, types)
|
77
|
+
nodes = nodes.dup
|
78
|
+
nodes[nodes.size...types.size] = nil
|
79
|
+
nodes.zip(types).reject do |node, type|
|
80
|
+
node_matches_type?(node, type)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def node_matches_type?(node, expected)
|
85
|
+
case expected
|
86
|
+
when :any
|
87
|
+
true
|
88
|
+
when nil
|
89
|
+
node.nil?
|
90
|
+
when Array
|
91
|
+
expected.any? { |exp| node_matches_type?(node, exp) }
|
92
|
+
when Class
|
93
|
+
node.is_a?(expected)
|
94
|
+
when Symbol
|
95
|
+
node.is_a?(Node) && node.type == expected
|
96
|
+
else
|
97
|
+
raise "Unrecognized expected type #{expected}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def inherited(subclass)
|
102
|
+
subclass.const_set :CHILDREN, self::CHILDREN.dup
|
103
|
+
subclass.const_set :CHILDREN_TYPES, self::CHILDREN_TYPES.dup
|
104
|
+
super
|
105
|
+
end
|
106
|
+
|
107
|
+
def update_children_const(name, rest: false)
|
108
|
+
children_map = self::CHILDREN
|
109
|
+
already_has_rest = false
|
110
|
+
children_map.each do |key, value|
|
111
|
+
if value.is_a? Range
|
112
|
+
children_map[key] = children_map[key].begin..(children_map[key].end - 1)
|
113
|
+
already_has_rest = key
|
114
|
+
elsif value < 0
|
115
|
+
children_map[key] -= 1
|
116
|
+
end
|
117
|
+
end
|
118
|
+
children_map[name] = if rest
|
119
|
+
if already_has_rest
|
120
|
+
raise "Class #{self} can't have extra children '#{name}' because it already has '#{name}' (#{children_map})"
|
121
|
+
end
|
122
|
+
children_map.size..-1
|
123
|
+
elsif already_has_rest
|
124
|
+
-1
|
125
|
+
else
|
126
|
+
children_map.size
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def define_accessor(name)
|
131
|
+
warn "child name '#{name}' conflicts with existing method for #{self}" if method_defined? name
|
132
|
+
class_eval <<-EVAL, __FILE__, __LINE__ + 1
|
133
|
+
def #{name}
|
134
|
+
children[CHILDREN.fetch(#{name.inspect})]
|
135
|
+
end
|
136
|
+
EVAL
|
137
|
+
end
|
138
|
+
|
139
|
+
def add_runtime_check(name, type)
|
140
|
+
self::CHILDREN_TYPES[name] = type
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Node::Mixin
|
5
|
+
module HasChildHandler
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
def call_child_handler(template, child, child_name = nil)
|
11
|
+
child_name ||= self.child_index_to_name(child.index)
|
12
|
+
method_name = format(template, name: child_name)
|
13
|
+
if respond_to?(method_name)
|
14
|
+
args = [child, child_name]
|
15
|
+
arity = method(method_name).arity
|
16
|
+
if arity >= 0
|
17
|
+
args = args[0...arity]
|
18
|
+
end
|
19
|
+
answer = send(method_name, *args)
|
20
|
+
end
|
21
|
+
answer
|
22
|
+
end
|
23
|
+
private :call_child_handler
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
def has_child_handler(template)
|
27
|
+
child_method_name = format(template, name: 'child')
|
28
|
+
action = template.gsub(/_%{name}/, '').gsub(/%{name}_/, '')
|
29
|
+
const_name = "#{Tools.camelize(action)}Handler"
|
30
|
+
class_eval <<-EVAL, __FILE__, __LINE__ + 1
|
31
|
+
module #{const_name} # module RewriteHandler
|
32
|
+
module ClassMethods # module ClassMethods
|
33
|
+
def has_child(#{action}: nil, **args) # def has_child(rewrite: nil, **args)
|
34
|
+
name, _types = args.first # name, _types = args.first
|
35
|
+
define_child_handler(#{template.inspect}, # define_child_handler('rewrite_%{child}',
|
36
|
+
name, #{action}) # name, rewrite)
|
37
|
+
super(**args) # super(**args)
|
38
|
+
end # end
|
39
|
+
end # end
|
40
|
+
|
41
|
+
def #{child_method_name}(child, name = nil) # def rewrite_child(child, name = nil)
|
42
|
+
call_child_handler(#{template.inspect}, child, # call_child_handler('rewrite_%{child}', child,
|
43
|
+
name) || super # name) || super
|
44
|
+
end # end
|
45
|
+
end # end
|
46
|
+
include #{const_name} # include RewriteHandler
|
47
|
+
singleton_class.prepend #{const_name}::ClassMethods # singleton_class.prepend RewriteHandler::ClassMethods
|
48
|
+
EVAL
|
49
|
+
end
|
50
|
+
|
51
|
+
def define_child_handler(template, name, action)
|
52
|
+
method_name = format(template, name: name)
|
53
|
+
case action
|
54
|
+
when nil
|
55
|
+
# Nothing to do
|
56
|
+
when Symbol
|
57
|
+
define_method(method_name) do |*args|
|
58
|
+
arity = method(action).arity
|
59
|
+
if arity < 0
|
60
|
+
send(action, *args)
|
61
|
+
else
|
62
|
+
send(action, *args[0...arity])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
when Proc
|
66
|
+
define_method(method_name, &action)
|
67
|
+
else
|
68
|
+
define_method(method_name) { |*| action }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
private :define_child_handler
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Node::Mixin
|
5
|
+
module HasTracker
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
TRACKERS = {}
|
10
|
+
|
11
|
+
def initialize(*)
|
12
|
+
@tracker_offset = tracker_storage.allocate_trackers(self.class::TRACKERS.size).begin
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def tracker_storage
|
17
|
+
covered_code.tracker_storage
|
18
|
+
end
|
19
|
+
|
20
|
+
def tracker_sources
|
21
|
+
self.class::TRACKERS.map do |name, _|
|
22
|
+
[:"#{name}_tracker", send(:"#{name}_tracker_source")]
|
23
|
+
end.to_h
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
def inherited(base)
|
28
|
+
base.const_set :TRACKERS, self::TRACKERS.dup
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def has_tracker(name)
|
33
|
+
i = self::TRACKERS[name] = self::TRACKERS.size
|
34
|
+
class_eval <<-EVAL, __FILE__, __LINE__ + 1
|
35
|
+
def #{name}_tracker_source
|
36
|
+
tracker_storage.tracker_source(@tracker_offset + #{i})
|
37
|
+
end
|
38
|
+
def #{name}_tracker_hits
|
39
|
+
tracker_storage[@tracker_offset + #{i}]
|
40
|
+
end
|
41
|
+
EVAL
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Node::Mixin
|
5
|
+
module IsStatement
|
6
|
+
def self.included(base)
|
7
|
+
base.has_child_handler('is_%{name}_statement')
|
8
|
+
end
|
9
|
+
|
10
|
+
def is_statement
|
11
|
+
parent.is_child_statement(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Default child rewriting rule
|
15
|
+
def is_child_statement(child, name = nil)
|
16
|
+
:if_incompatible
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Node::Mixin
|
5
|
+
module Rewriting
|
6
|
+
def self.included(base)
|
7
|
+
base.has_child_handler('rewrite_%{name}')
|
8
|
+
end
|
9
|
+
|
10
|
+
# Code to add before and after the node for covering purposes
|
11
|
+
def rewrite
|
12
|
+
end
|
13
|
+
|
14
|
+
# Default child rewriting rule
|
15
|
+
def rewrite_child(child, name = nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Replaces all the '%{local}' or '%{some_tracker}' in rewriting rules
|
19
|
+
def resolve_rewrite(rule, context)
|
20
|
+
return if rule == nil
|
21
|
+
sources = context.tracker_sources
|
22
|
+
format(rule, local: covered_code.local_var, node: '%{node}', **sources)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns an array of [range, rule], where rule is a string containing '%{node}'
|
26
|
+
# Rules must be ordered inner-most first
|
27
|
+
def rewriting_rules
|
28
|
+
[
|
29
|
+
resolve_rewrite(rewrite, self),
|
30
|
+
resolve_rewrite(parent.rewrite_child(self), parent),
|
31
|
+
].compact.map { |rule| [expression, rule] }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'const'
|
4
|
+
|
5
|
+
module DeepCover
|
6
|
+
class Node
|
7
|
+
class ModuleName < Node
|
8
|
+
has_child scope: [Node, nil]
|
9
|
+
has_child const_name: Symbol
|
10
|
+
|
11
|
+
def flow_completion_count
|
12
|
+
parent.execution_count
|
13
|
+
end
|
14
|
+
|
15
|
+
def execution_count
|
16
|
+
if scope
|
17
|
+
scope.flow_completion_count
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Module < Node
|
25
|
+
check_completion
|
26
|
+
has_tracker :body_entry
|
27
|
+
has_child const: {const: ModuleName}
|
28
|
+
has_child body: Node,
|
29
|
+
can_be_empty: -> { base_node.loc.end.begin },
|
30
|
+
rewrite: '%{body_entry_tracker};%{local}=nil;%{node}',
|
31
|
+
is_statement: true,
|
32
|
+
flow_entry_count: :body_entry_tracker_hits
|
33
|
+
executed_loc_keys :keyword
|
34
|
+
|
35
|
+
def execution_count
|
36
|
+
body_entry_tracker_hits
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Class < Node
|
41
|
+
check_completion
|
42
|
+
has_tracker :body_entry
|
43
|
+
has_child const: {const: ModuleName}
|
44
|
+
has_child inherit: [Node, nil] # TODO
|
45
|
+
has_child body: Node,
|
46
|
+
can_be_empty: -> { base_node.loc.end.begin },
|
47
|
+
rewrite: '%{body_entry_tracker};%{node}',
|
48
|
+
is_statement: true,
|
49
|
+
flow_entry_count: :body_entry_tracker_hits
|
50
|
+
executed_loc_keys :keyword
|
51
|
+
|
52
|
+
def execution_count
|
53
|
+
body_entry_tracker_hits
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# class << foo
|
58
|
+
class Sclass < Node
|
59
|
+
has_child object: Node
|
60
|
+
has_child body: Node,
|
61
|
+
can_be_empty: -> { base_node.loc.end.begin },
|
62
|
+
is_statement: true
|
63
|
+
# TODO
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
class Node::Root < Node
|
5
|
+
has_tracker :root
|
6
|
+
has_child main: Node,
|
7
|
+
can_be_empty: -> { Parser::Source::Range.new(covered_code.buffer, 0, 0) },
|
8
|
+
is_statement: true,
|
9
|
+
rewrite: -> {
|
10
|
+
"#{tracker_storage.setup_source};%{root_tracker};%{local}=nil;%{node}"
|
11
|
+
}
|
12
|
+
attr_reader :covered_code
|
13
|
+
alias_method :flow_entry_count, :root_tracker_hits
|
14
|
+
|
15
|
+
def initialize(child_ast, covered_code)
|
16
|
+
@covered_code = covered_code
|
17
|
+
super(nil, parent: nil, base_children: [child_ast])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|