deep-cover 0.1.1 → 0.1.2
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/.rspec +1 -0
- data/.travis.yml +3 -1
- data/Rakefile +1 -1
- data/bin/selfcov +1 -1
- data/deep_cover.gemspec +2 -1
- data/lib/deep_cover/analyser.rb +1 -2
- data/lib/deep_cover/analyser/base.rb +20 -6
- data/lib/deep_cover/analyser/covered_code_source.rb +0 -12
- data/lib/deep_cover/analyser/node.rb +11 -1
- data/lib/deep_cover/analyser/optionally_covered.rb +14 -0
- data/lib/deep_cover/auto_run.rb +36 -32
- data/lib/deep_cover/backports.rb +9 -0
- data/lib/deep_cover/base.rb +8 -1
- data/lib/deep_cover/cli/debugger.rb +6 -4
- data/lib/deep_cover/cli/deep_cover.rb +37 -6
- data/lib/deep_cover/cli/instrumented_clone_reporter.rb +50 -32
- data/lib/deep_cover/config.rb +16 -7
- data/lib/deep_cover/coverage.rb +8 -9
- data/lib/deep_cover/covered_code.rb +17 -18
- data/lib/deep_cover/node/base.rb +28 -4
- data/lib/deep_cover/node/branch.rb +10 -7
- data/lib/deep_cover/node/def.rb +2 -2
- data/lib/deep_cover/node/empty_body.rb +1 -1
- data/lib/deep_cover/node/mixin/can_augment_children.rb +1 -2
- data/lib/deep_cover/node/mixin/execution_location.rb +9 -6
- data/lib/deep_cover/node/mixin/has_child.rb +16 -17
- data/lib/deep_cover/node/mixin/has_tracker.rb +2 -6
- data/lib/deep_cover/node/mixin/rewriting.rb +9 -8
- data/lib/deep_cover/node/send.rb +57 -48
- data/lib/deep_cover/node/{boolean.rb → short_circuit.rb} +0 -0
- data/lib/deep_cover/reporter/istanbul.rb +2 -3
- data/lib/deep_cover/tools.rb +2 -1
- data/lib/deep_cover/tools/dasherize.rb +8 -0
- data/lib/deep_cover/tools/dump_covered_code.rb +12 -6
- data/lib/deep_cover/tools/format_char_cover.rb +2 -3
- data/lib/deep_cover/tools/slice.rb +7 -0
- data/lib/deep_cover/version.rb +1 -1
- metadata +7 -18
data/lib/deep_cover/config.rb
CHANGED
@@ -6,8 +6,8 @@ module DeepCover
|
|
6
6
|
allow_partial: false,
|
7
7
|
}
|
8
8
|
|
9
|
-
def initialize
|
10
|
-
@options = copy(DEFAULTS)
|
9
|
+
def initialize(**options)
|
10
|
+
@options = copy(DEFAULTS.merge(options))
|
11
11
|
end
|
12
12
|
|
13
13
|
def to_hash
|
@@ -16,12 +16,14 @@ module DeepCover
|
|
16
16
|
alias_method :to_h, :to_hash
|
17
17
|
|
18
18
|
def ignore_uncovered(*keywords)
|
19
|
-
|
19
|
+
check_uncovered(keywords)
|
20
|
+
@options[:ignore_uncovered] += keywords
|
20
21
|
self
|
21
22
|
end
|
22
23
|
|
23
24
|
def detect_uncovered(*keywords)
|
24
|
-
|
25
|
+
check_uncovered(keywords)
|
26
|
+
@options[:ignore_uncovered] -= keywords
|
25
27
|
self
|
26
28
|
end
|
27
29
|
|
@@ -31,20 +33,27 @@ module DeepCover
|
|
31
33
|
end
|
32
34
|
|
33
35
|
private
|
36
|
+
def check_uncovered(keywords)
|
37
|
+
unknown = keywords - Analyser.optionally_covered
|
38
|
+
raise ArgumentError, "unknown options: #{unknown.join(', ')}" unless unknown.empty?
|
39
|
+
end
|
40
|
+
|
34
41
|
def copy(h)
|
35
42
|
h.dup.transform_values(&:dup)
|
36
43
|
end
|
37
44
|
|
38
45
|
module Setter
|
39
|
-
def
|
46
|
+
def config
|
40
47
|
@config ||= Config.new
|
48
|
+
end
|
41
49
|
|
50
|
+
def configure(&block)
|
42
51
|
raise "Must provide a block" unless block
|
43
52
|
case block.arity
|
44
53
|
when 0
|
45
|
-
|
54
|
+
config.instance_eval(&block)
|
46
55
|
when 1
|
47
|
-
block.call(
|
56
|
+
block.call(config)
|
48
57
|
end
|
49
58
|
end
|
50
59
|
end
|
data/lib/deep_cover/coverage.rb
CHANGED
@@ -4,7 +4,6 @@ module DeepCover
|
|
4
4
|
require 'parser/current'
|
5
5
|
end
|
6
6
|
require 'pry'
|
7
|
-
require 'pathname'
|
8
7
|
require_relative 'covered_code'
|
9
8
|
require 'securerandom'
|
10
9
|
|
@@ -29,9 +28,9 @@ module DeepCover
|
|
29
28
|
covered_code(filename).line_coverage(**options)
|
30
29
|
end
|
31
30
|
|
32
|
-
def covered_code(path)
|
31
|
+
def covered_code(path, **options)
|
33
32
|
raise 'path must be an absolute path' unless Pathname.new(path).absolute?
|
34
|
-
@covered_codes[path] ||= CoveredCode.new(path: path, **@options)
|
33
|
+
@covered_codes[path] ||= CoveredCode.new(path: path, **options, **@options)
|
35
34
|
end
|
36
35
|
|
37
36
|
def each
|
@@ -57,7 +56,7 @@ module DeepCover
|
|
57
56
|
|
58
57
|
def report_istanbul(output: nil, **options)
|
59
58
|
dir = output_istanbul(**options).dirname
|
60
|
-
|
59
|
+
unless [nil, '', 'false'].include? output
|
61
60
|
output = File.expand_path(output)
|
62
61
|
html = "--reporter=html --report-dir='#{output}' && open '#{output}/index.html'"
|
63
62
|
end
|
@@ -89,8 +88,8 @@ module DeepCover
|
|
89
88
|
end
|
90
89
|
end
|
91
90
|
|
92
|
-
def self.load(dest_path, dirname = 'deep_cover')
|
93
|
-
Persistence.new(dest_path, dirname).load
|
91
|
+
def self.load(dest_path, dirname = 'deep_cover', with_trackers: true)
|
92
|
+
Persistence.new(dest_path, dirname).load(with_trackers: with_trackers)
|
94
93
|
end
|
95
94
|
|
96
95
|
def self.saved?(dest_path, dirname = 'deep_cover')
|
@@ -108,7 +107,7 @@ module DeepCover
|
|
108
107
|
end
|
109
108
|
|
110
109
|
def tracker_global
|
111
|
-
@options
|
110
|
+
@options.fetch(:tracker_global, CoveredCode::DEFAULT_TRACKER_GLOBAL)
|
112
111
|
end
|
113
112
|
|
114
113
|
class Persistence
|
@@ -120,9 +119,9 @@ module DeepCover
|
|
120
119
|
@dir_path = Pathname(dest_path).join(dirname).expand_path
|
121
120
|
end
|
122
121
|
|
123
|
-
def load
|
122
|
+
def load(with_trackers: true)
|
124
123
|
saved?
|
125
|
-
load_trackers
|
124
|
+
load_trackers if with_trackers
|
126
125
|
load_coverage
|
127
126
|
end
|
128
127
|
|
@@ -1,10 +1,12 @@
|
|
1
1
|
module DeepCover
|
2
2
|
class CoveredCode
|
3
|
-
|
3
|
+
DEFAULT_TRACKER_GLOBAL = '$_cov'
|
4
|
+
|
5
|
+
attr_accessor :covered_source, :buffer, :tracker_global, :local_var, :name
|
4
6
|
@@counter = 0
|
5
7
|
@@globals = Hash.new{|h, global| h[global] = eval("#{global} ||= {}") }
|
6
8
|
|
7
|
-
def initialize(path: nil, source: nil, lineno: nil, tracker_global:
|
9
|
+
def initialize(path: nil, source: nil, lineno: nil, tracker_global: DEFAULT_TRACKER_GLOBAL, local_var: '_temp', name: nil)
|
8
10
|
raise "Must provide either path or source" unless path || source
|
9
11
|
|
10
12
|
@buffer = ::Parser::Source::Buffer.new(path)
|
@@ -17,6 +19,7 @@ module DeepCover
|
|
17
19
|
@tracker_count = 0
|
18
20
|
@tracker_global = tracker_global
|
19
21
|
@local_var = local_var
|
22
|
+
@name = name || (source ? '(source)' : File.basename(path))
|
20
23
|
@covered_source = instrument_source
|
21
24
|
end
|
22
25
|
|
@@ -24,10 +27,6 @@ module DeepCover
|
|
24
27
|
@buffer.name || "(source: '#{@buffer.source[0..20]}...')"
|
25
28
|
end
|
26
29
|
|
27
|
-
def name
|
28
|
-
@buffer.name ? File.basename(@buffer.name) : "(source)"
|
29
|
-
end
|
30
|
-
|
31
30
|
def nb_lines
|
32
31
|
@nb_lines ||= begin
|
33
32
|
lines = buffer.source_lines
|
@@ -95,7 +94,7 @@ module DeepCover
|
|
95
94
|
|
96
95
|
def root
|
97
96
|
@root ||= begin
|
98
|
-
ast =
|
97
|
+
ast = DeepCover.parser.parse(@buffer)
|
99
98
|
Node::Root.new(ast, self)
|
100
99
|
end
|
101
100
|
end
|
@@ -106,17 +105,17 @@ module DeepCover
|
|
106
105
|
|
107
106
|
def instrument_source
|
108
107
|
rewriter = ::Parser::Source::Rewriter.new(@buffer)
|
109
|
-
covered_ast.each_node do |node|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
108
|
+
covered_ast.each_node(:postorder) do |node|
|
109
|
+
node.rewriting_rules.each do |range, rule|
|
110
|
+
prefix, _node, suffix = rule.partition('%{node}')
|
111
|
+
unless prefix.empty?
|
112
|
+
prefix = yield prefix, node, range.begin, :prefix if block_given?
|
113
|
+
rewriter.insert_before_multi range, prefix rescue binding.pry
|
114
|
+
end
|
115
|
+
unless suffix.empty?
|
116
|
+
suffix = yield suffix, node, range.end, :suffix if block_given?
|
117
|
+
rewriter.insert_after_multi range, suffix
|
118
|
+
end
|
120
119
|
end
|
121
120
|
end
|
122
121
|
rewriter.process
|
data/lib/deep_cover/node/base.rb
CHANGED
@@ -19,17 +19,25 @@ module DeepCover
|
|
19
19
|
@base_node = base_node
|
20
20
|
@parent = parent
|
21
21
|
@index = index
|
22
|
-
@children =
|
22
|
+
@children = begin
|
23
|
+
augment_children(base_children)
|
24
|
+
rescue StandardError => e
|
25
|
+
diagnose(e)
|
26
|
+
end
|
23
27
|
super()
|
24
28
|
end
|
25
29
|
|
26
|
-
###
|
30
|
+
### Public API
|
27
31
|
|
32
|
+
# Shortcut to access children
|
28
33
|
def [](v)
|
29
34
|
children[v]
|
30
35
|
end
|
31
36
|
|
32
|
-
|
37
|
+
# Shortcut to create a node from source code
|
38
|
+
def self.[](source)
|
39
|
+
CoveredCode.new(source: source).execute_code.covered_ast
|
40
|
+
end
|
33
41
|
|
34
42
|
def children_nodes
|
35
43
|
children.select{|c| c.is_a? Node }
|
@@ -75,7 +83,7 @@ module DeepCover
|
|
75
83
|
end
|
76
84
|
|
77
85
|
def type
|
78
|
-
return base_node.type if base_node
|
86
|
+
return base_node.type if base_node.respond_to? :type
|
79
87
|
self.class.name.split('::').last.to_sym
|
80
88
|
end
|
81
89
|
|
@@ -95,5 +103,21 @@ module DeepCover
|
|
95
103
|
t.casecmp(class_name) == 0 ? t : "#{t}[#{class_name}]"
|
96
104
|
end
|
97
105
|
|
106
|
+
private
|
107
|
+
def diagnose(exception)
|
108
|
+
if self.class == Node
|
109
|
+
exp = base_node.loc.expression
|
110
|
+
warn ["Unknown node type encountered: #{base_node.type}",
|
111
|
+
'This node will not be handled properly; its subnodes will be ignored',
|
112
|
+
'Source:',
|
113
|
+
exp && exp.source,
|
114
|
+
"Original exception:",
|
115
|
+
exception.inspect,
|
116
|
+
].join("\n")
|
117
|
+
[]
|
118
|
+
else
|
119
|
+
raise exception
|
120
|
+
end
|
121
|
+
end
|
98
122
|
end
|
99
123
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative 'empty_body'
|
2
|
+
|
1
3
|
module DeepCover
|
2
4
|
class Node
|
3
5
|
module Branch
|
@@ -13,14 +15,15 @@ module DeepCover
|
|
13
15
|
# Also define flow_entry_count
|
14
16
|
end
|
15
17
|
|
16
|
-
class TrivialBranch <
|
17
|
-
def
|
18
|
-
condition
|
18
|
+
class TrivialBranch < Node::EmptyBody
|
19
|
+
def initialize(condition, other_branch, position: true)
|
20
|
+
@condition = condition
|
21
|
+
@other_branch = other_branch
|
22
|
+
super(nil, parent: condition.parent, position: position)
|
19
23
|
end
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
true
|
24
|
+
|
25
|
+
def flow_entry_count
|
26
|
+
@condition.flow_completion_count - @other_branch.flow_entry_count
|
24
27
|
end
|
25
28
|
end
|
26
29
|
|
data/lib/deep_cover/node/def.rb
CHANGED
@@ -7,7 +7,7 @@ module DeepCover
|
|
7
7
|
has_child method_name: Symbol
|
8
8
|
has_child signature: Args
|
9
9
|
has_child body: Node,
|
10
|
-
rewrite: '%{method_call_tracker};%{local}=nil;%{node}
|
10
|
+
rewrite: '%{method_call_tracker};%{local}=nil;%{node}',
|
11
11
|
can_be_empty: -> { base_node.loc.end.begin },
|
12
12
|
is_statement: true,
|
13
13
|
flow_entry_count: :method_call_tracker_hits
|
@@ -25,7 +25,7 @@ module DeepCover
|
|
25
25
|
has_child method_name: Symbol
|
26
26
|
has_child signature: Args
|
27
27
|
has_child body: Node,
|
28
|
-
rewrite: '%{method_call_tracker};%{local}=nil;%{node}
|
28
|
+
rewrite: '%{method_call_tracker};%{local}=nil;%{node}',
|
29
29
|
can_be_empty: -> { base_node.loc.end.begin },
|
30
30
|
is_statement: true,
|
31
31
|
flow_entry_count: :method_call_tracker_hits
|
@@ -2,7 +2,6 @@ module DeepCover
|
|
2
2
|
class Node::EmptyBody < Node
|
3
3
|
def initialize(base_node, parent: raise, index: 0, position: ChildCanBeEmpty.last_empty_position)
|
4
4
|
@position = position
|
5
|
-
@position = parent.expression.begin if @position == true # Some random position... don't rewrite!
|
6
5
|
super(base_node, parent: parent, index: index, base_children: [])
|
7
6
|
end
|
8
7
|
|
@@ -11,6 +10,7 @@ module DeepCover
|
|
11
10
|
end
|
12
11
|
|
13
12
|
def loc_hash
|
13
|
+
return {} if @position == true
|
14
14
|
{expression: @position}
|
15
15
|
end
|
16
16
|
|
@@ -15,8 +15,7 @@ module DeepCover
|
|
15
15
|
child_base_nodes = [*child_base_nodes, *Array.new(missing)]
|
16
16
|
end
|
17
17
|
child_base_nodes.map.with_index do |child, child_index|
|
18
|
-
child_name = self.class.child_index_to_name(child_index, child_base_nodes.size)
|
19
|
-
|
18
|
+
child_name = self.class.child_index_to_name(child_index, child_base_nodes.size)
|
20
19
|
if (klass = remap_child(child, child_name))
|
21
20
|
klass.new(child, parent: self, index: child_index)
|
22
21
|
else
|
@@ -25,17 +25,20 @@ module DeepCover
|
|
25
25
|
nil
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
28
|
+
def executed_loc_hash
|
29
|
+
h = Tools.slice(loc_hash, *executed_loc_keys)
|
29
30
|
if (keys = parent.child_executed_loc_keys(self))
|
30
|
-
|
31
|
+
h.merge!(Tools.slice(parent.loc_hash, *keys))
|
31
32
|
end
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
h.reject{|k, v| v.nil? }
|
34
|
+
end
|
35
|
+
|
36
|
+
def executed_locs
|
37
|
+
executed_loc_hash.values
|
35
38
|
end
|
36
39
|
|
37
40
|
def loc_hash
|
38
|
-
@loc_hash ||= base_node.location.to_hash
|
41
|
+
@loc_hash ||= (base_node.respond_to?(:location) ? base_node.location.to_hash : {}).freeze
|
39
42
|
end
|
40
43
|
|
41
44
|
def expression
|
@@ -3,14 +3,9 @@ module DeepCover
|
|
3
3
|
module HasChild
|
4
4
|
def self.included(base)
|
5
5
|
base.extend ClassMethods
|
6
|
-
setup_constants(base)
|
7
6
|
end
|
8
|
-
|
9
|
-
|
10
|
-
subclass.const_set :CHILDREN, {}
|
11
|
-
subclass.const_set :CHILDREN_TYPES, {}
|
12
|
-
end
|
13
|
-
|
7
|
+
CHILDREN = {}
|
8
|
+
CHILDREN_TYPES = {}
|
14
9
|
|
15
10
|
def initialize(*)
|
16
11
|
super
|
@@ -25,18 +20,25 @@ module DeepCover
|
|
25
20
|
end
|
26
21
|
|
27
22
|
module ClassMethods
|
28
|
-
def has_child(
|
23
|
+
def has_child(_rest: false, _refine: false, **h)
|
29
24
|
raise "Needs exactly one custom named argument, got #{h.size}" if h.size != 1
|
30
25
|
name, types = h.first
|
31
26
|
raise TypeError, "Expect a Symbol for name, got a #{name.class} (#{name.inspect})" unless name.is_a?(Symbol)
|
32
|
-
update_children_const(name, rest:
|
33
|
-
define_accessor(name)
|
27
|
+
update_children_const(name, rest: _rest) unless _refine
|
28
|
+
define_accessor(name) unless _refine
|
34
29
|
add_runtime_check(name, types)
|
35
30
|
self
|
36
31
|
end
|
37
32
|
|
38
33
|
def has_extra_children(**h)
|
39
|
-
has_child(**h,
|
34
|
+
has_child(**h, _rest: true)
|
35
|
+
end
|
36
|
+
|
37
|
+
def refine_child(child_name = nil, **h)
|
38
|
+
if child_name
|
39
|
+
h = {child_name => self::CHILDREN_TYPES.fetch(child_name), **h}
|
40
|
+
end
|
41
|
+
has_child(**h, _refine: true)
|
40
42
|
end
|
41
43
|
|
42
44
|
def child_index_to_name(index, nb_children)
|
@@ -91,14 +93,11 @@ module DeepCover
|
|
91
93
|
end
|
92
94
|
|
93
95
|
def inherited(subclass)
|
94
|
-
|
96
|
+
subclass.const_set :CHILDREN, self::CHILDREN.dup
|
97
|
+
subclass.const_set :CHILDREN_TYPES, self::CHILDREN_TYPES.dup
|
95
98
|
super
|
96
99
|
end
|
97
100
|
|
98
|
-
def const_missing(name)
|
99
|
-
const_set(name, self::CHILDREN.fetch(name.downcase) { return super })
|
100
|
-
end
|
101
|
-
|
102
101
|
def update_children_const(name, rest: false)
|
103
102
|
children_map = self::CHILDREN
|
104
103
|
already_has_rest = false
|
@@ -124,7 +123,7 @@ module DeepCover
|
|
124
123
|
warn "child name '#{name}' conflicts with existing method for #{self}" if method_defined? name
|
125
124
|
class_eval <<-end_eval, __FILE__, __LINE__
|
126
125
|
def #{name}
|
127
|
-
children[#{name.
|
126
|
+
children[CHILDREN.fetch(#{name.inspect})]
|
128
127
|
end
|
129
128
|
end_eval
|
130
129
|
end
|
@@ -3,12 +3,8 @@ module DeepCover
|
|
3
3
|
module HasTracker
|
4
4
|
def self.included(base)
|
5
5
|
base.extend ClassMethods
|
6
|
-
setup_constants(base)
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.setup_constants(base)
|
10
|
-
base.const_set :TRACKERS, {}
|
11
6
|
end
|
7
|
+
TRACKERS = {}
|
12
8
|
|
13
9
|
def initialize(*)
|
14
10
|
@tracker_offset = covered_code.allocate_trackers(self.class::TRACKERS.size).begin
|
@@ -23,8 +19,8 @@ module DeepCover
|
|
23
19
|
|
24
20
|
module ClassMethods
|
25
21
|
def inherited(base)
|
22
|
+
base.const_set :TRACKERS, self::TRACKERS.dup
|
26
23
|
super
|
27
|
-
HasTracker.setup_constants(base)
|
28
24
|
end
|
29
25
|
|
30
26
|
def has_tracker(name)
|