deep-cover-core 0.6.3.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|