deep-cover 0.1.14 → 0.1.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +227 -0
- data/Gemfile +5 -2
- data/Rakefile +9 -6
- data/bin/console +3 -3
- data/bin/cov +8 -8
- data/bin/gemcov +2 -2
- data/bin/selfcov +5 -5
- data/bin/test_gems +11 -10
- data/bin/testall +6 -6
- data/deep_cover.gemspec +26 -21
- data/exe/deep-cover +1 -0
- data/lib/deep-cover.rb +2 -0
- data/lib/deep_cover.rb +3 -0
- data/lib/deep_cover/analyser.rb +2 -0
- data/lib/deep_cover/analyser/base.rb +2 -0
- data/lib/deep_cover/analyser/branch.rb +4 -2
- data/lib/deep_cover/analyser/covered_code_source.rb +3 -1
- data/lib/deep_cover/analyser/function.rb +3 -1
- data/lib/deep_cover/analyser/ignore_uncovered.rb +6 -4
- data/lib/deep_cover/analyser/node.rb +3 -0
- data/lib/deep_cover/analyser/optionally_covered.rb +12 -7
- data/lib/deep_cover/analyser/per_char.rb +7 -6
- data/lib/deep_cover/analyser/per_line.rb +9 -8
- data/lib/deep_cover/analyser/statement.rb +2 -0
- data/lib/deep_cover/analyser/subset.rb +4 -1
- data/lib/deep_cover/auto_run.rb +3 -0
- data/lib/deep_cover/autoload_tracker.rb +6 -3
- data/lib/deep_cover/backports.rb +2 -0
- data/lib/deep_cover/base.rb +11 -2
- data/lib/deep_cover/builtin_takeover.rb +2 -0
- data/lib/deep_cover/cli/debugger.rb +55 -30
- data/lib/deep_cover/cli/deep_cover.rb +17 -11
- data/lib/deep_cover/cli/instrumented_clone_reporter.rb +16 -14
- data/lib/deep_cover/config.rb +29 -16
- data/lib/deep_cover/core_ext/autoload_overrides.rb +2 -0
- data/lib/deep_cover/core_ext/coverage_replacement.rb +2 -0
- data/lib/deep_cover/core_ext/load_overrides.rb +5 -6
- data/lib/deep_cover/core_ext/require_overrides.rb +6 -7
- data/lib/deep_cover/coverage.rb +21 -18
- data/lib/deep_cover/covered_code.rb +22 -12
- data/lib/deep_cover/custom_requirer.rb +82 -35
- data/lib/deep_cover/memoize.rb +48 -0
- data/lib/deep_cover/module_override.rb +2 -0
- data/lib/deep_cover/node.rb +14 -1
- data/lib/deep_cover/node/arguments.rb +2 -0
- data/lib/deep_cover/node/assignments.rb +32 -30
- data/lib/deep_cover/node/base.rb +30 -29
- data/lib/deep_cover/node/begin.rb +3 -1
- data/lib/deep_cover/node/block.rb +5 -2
- data/lib/deep_cover/node/branch.rb +2 -1
- data/lib/deep_cover/node/case.rb +15 -13
- data/lib/deep_cover/node/collections.rb +2 -0
- data/lib/deep_cover/node/const.rb +2 -0
- data/lib/deep_cover/node/def.rb +10 -8
- data/lib/deep_cover/node/empty_body.rb +2 -0
- data/lib/deep_cover/node/exceptions.rb +3 -1
- data/lib/deep_cover/node/if.rb +3 -1
- data/lib/deep_cover/node/keywords.rb +4 -2
- data/lib/deep_cover/node/literals.rb +2 -0
- data/lib/deep_cover/node/loops.rb +5 -3
- data/lib/deep_cover/node/mixin/can_augment_children.rb +8 -7
- data/lib/deep_cover/node/mixin/check_completion.rb +3 -1
- data/lib/deep_cover/node/mixin/child_can_be_empty.rb +4 -2
- data/lib/deep_cover/node/mixin/executed_after_children.rb +2 -0
- data/lib/deep_cover/node/mixin/execution_location.rb +4 -2
- data/lib/deep_cover/node/mixin/flow_accounting.rb +2 -0
- data/lib/deep_cover/node/mixin/has_child.rb +22 -18
- data/lib/deep_cover/node/mixin/has_child_handler.rb +10 -8
- data/lib/deep_cover/node/mixin/has_tracker.rb +4 -2
- data/lib/deep_cover/node/mixin/is_statement.rb +3 -1
- data/lib/deep_cover/node/mixin/rewriting.rb +5 -3
- data/lib/deep_cover/node/mixin/wrapper.rb +2 -0
- data/lib/deep_cover/node/module.rb +11 -9
- data/lib/deep_cover/node/root.rb +2 -0
- data/lib/deep_cover/node/send.rb +4 -2
- data/lib/deep_cover/node/short_circuit.rb +4 -2
- data/lib/deep_cover/node/splat.rb +2 -0
- data/lib/deep_cover/node/variables.rb +2 -0
- data/lib/deep_cover/parser_ext/range.rb +3 -1
- data/lib/deep_cover/problem_with_diagnostic.rb +11 -9
- data/lib/deep_cover/reporter.rb +2 -0
- data/lib/deep_cover/reporter/istanbul.rb +28 -24
- data/lib/deep_cover/tools.rb +2 -0
- data/lib/deep_cover/tools/builtin_coverage.rb +6 -4
- data/lib/deep_cover/tools/camelize.rb +3 -1
- data/lib/deep_cover/tools/dasherize.rb +3 -1
- data/lib/deep_cover/tools/dump_covered_code.rb +7 -6
- data/lib/deep_cover/tools/execute_sample.rb +13 -13
- data/lib/deep_cover/tools/format.rb +3 -1
- data/lib/deep_cover/tools/format_char_cover.rb +4 -2
- data/lib/deep_cover/tools/format_generated_code.rb +3 -1
- data/lib/deep_cover/tools/number_lines.rb +2 -0
- data/lib/deep_cover/tools/our_coverage.rb +5 -3
- data/lib/deep_cover/tools/profiling.rb +66 -0
- data/lib/deep_cover/tools/require_relative_dir.rb +3 -1
- data/lib/deep_cover/tools/silence_warnings.rb +4 -1
- data/lib/deep_cover/tools/slice.rb +3 -1
- data/lib/deep_cover/tools/truncate_backtrace.rb +2 -0
- data/lib/deep_cover/version.rb +3 -1
- metadata +47 -30
data/exe/deep-cover
CHANGED
data/lib/deep-cover.rb
CHANGED
data/lib/deep_cover.rb
CHANGED
data/lib/deep_cover/analyser.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'subset'
|
2
4
|
|
3
5
|
module DeepCover
|
4
6
|
class Analyser::Branch < Analyser
|
5
7
|
include Analyser::Subset
|
6
|
-
SUBSET_CLASSES = [Node::Branch]
|
8
|
+
SUBSET_CLASSES = [Node::Branch].freeze
|
7
9
|
|
8
10
|
def results
|
9
11
|
each_node.map do |node, _children|
|
10
|
-
branches_runs = node.branches.map{|b| [b, node_runs(b)]}.to_h
|
12
|
+
branches_runs = node.branches.map { |b| [b, node_runs(b)] }.to_h
|
11
13
|
[node, branches_runs]
|
12
14
|
end.to_h
|
13
15
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DeepCover
|
2
4
|
class Analyser::CoveredCodeSource < Analyser
|
3
5
|
attr_reader :covered_code
|
4
6
|
|
5
7
|
def initialize(covered_code)
|
6
|
-
@covered_code = covered_code
|
8
|
+
@covered_code = covered_code.freeze
|
7
9
|
end
|
8
10
|
|
9
11
|
# Looking exclusively at our subset of nodes, returns the node's direct descendants
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'subset'
|
2
4
|
|
3
5
|
module DeepCover
|
4
6
|
class Analyser::Function < Analyser
|
5
7
|
include Analyser::Subset
|
6
|
-
SUBSET_CLASSES = [Node::Block, Node::Defs, Node::Def]
|
8
|
+
SUBSET_CLASSES = [Node::Block, Node::Defs, Node::Def].freeze
|
7
9
|
|
8
10
|
def node_runs(node)
|
9
11
|
super(node.body)
|
@@ -1,16 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DeepCover
|
2
4
|
module Analyser::IgnoreUncovered
|
3
5
|
def initialize(source, ignore_uncovered: [], **options)
|
4
6
|
super
|
5
7
|
@allow_filters = Array(ignore_uncovered)
|
6
|
-
|
7
|
-
|
8
|
-
|
8
|
+
.map { |kind| :"is_#{kind}?" }
|
9
|
+
.select { |name| respond_to?(name) }
|
10
|
+
.map { |name| method(name) } # So was tempted to write `.map(&method(:method))`!
|
9
11
|
end
|
10
12
|
|
11
13
|
def node_runs(node)
|
12
14
|
runs = super
|
13
|
-
if runs == 0 && @allow_filters.any?{ |f| f[node] }
|
15
|
+
if runs == 0 && @allow_filters.any? { |f| f[node] }
|
14
16
|
runs = nil
|
15
17
|
end
|
16
18
|
runs
|
@@ -1,14 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DeepCover
|
2
4
|
module Analyser::OptionallyCovered
|
3
5
|
def optionally_covered
|
4
6
|
@optionally_covered ||= Analyser
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
.constants.map { |c| Analyser.const_get(c) }
|
8
|
+
.select { |klass| klass < Analyser }
|
9
|
+
.flat_map do |klass|
|
10
|
+
klass.instance_methods(false).map do |method|
|
11
|
+
method =~ /^is_(.*)\?$/
|
12
|
+
Regexp.last_match(1)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
.compact
|
16
|
+
.map(&:to_sym)
|
12
17
|
end
|
13
18
|
end
|
14
19
|
end
|
@@ -1,19 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DeepCover
|
2
4
|
class Analyser::PerChar < Analyser
|
3
5
|
# Returns an array of characters for each line of code.
|
4
6
|
# Each character is either ' ' (executed), '-' (not executable) or 'x' (not covered)
|
5
7
|
def results
|
6
8
|
buffer = covered_code.buffer
|
7
|
-
bc = buffer.source_lines.map{|line| '-' * line.size}
|
9
|
+
bc = buffer.source_lines.map { |line| '-' * line.size }
|
8
10
|
each_node do |node, _children|
|
9
11
|
runs = node_runs(node)
|
10
|
-
if runs
|
11
|
-
|
12
|
-
|
13
|
-
end
|
12
|
+
next if runs == nil
|
13
|
+
node.proper_range.each do |pos|
|
14
|
+
bc[buffer.line_for_position(pos) - buffer.first_line][buffer.column_for_position(pos)] = runs > 0 ? ' ' : 'x'
|
14
15
|
end
|
15
16
|
end
|
16
|
-
bc.zip(buffer.source_lines){|cov, line| cov[line.size..-1] = ''} # remove extraneous character for end lines, in any
|
17
|
+
bc.zip(buffer.source_lines) { |cov, line| cov[line.size..-1] = '' } # remove extraneous character for end lines, in any
|
17
18
|
bc
|
18
19
|
end
|
19
20
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DeepCover
|
2
4
|
class Analyser::PerLine < Analyser
|
3
5
|
# Returns an array of runs, one per line.
|
@@ -5,15 +7,14 @@ module DeepCover
|
|
5
7
|
disallow_partial = !options.fetch(:allow_partial, true)
|
6
8
|
line_hits = Array.new(covered_code.nb_lines + covered_code.lineno - 1)
|
7
9
|
each_node do |node, _children|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
line_hits[lineno] = [line_hits[lineno] || 0, runs].max
|
10
|
+
next unless (runs = node_runs(node))
|
11
|
+
node.executed_locs.each do |loc|
|
12
|
+
lineno = loc.line - 1
|
13
|
+
if disallow_partial
|
14
|
+
line_hits[lineno] = 0 if runs == 0
|
15
|
+
next if line_hits[lineno] == 0
|
16
16
|
end
|
17
|
+
line_hits[lineno] = [line_hits[lineno] || 0, runs].max
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DeepCover
|
2
4
|
# A module to create a subset from a criteria called `in_subset?`
|
3
5
|
# Including classes can refine it, or specify SUBSET_CLASSES
|
@@ -7,6 +9,7 @@ module DeepCover
|
|
7
9
|
end
|
8
10
|
|
9
11
|
private
|
12
|
+
|
10
13
|
def find_children(from, parent = from)
|
11
14
|
@source.node_children(from).flat_map do |node|
|
12
15
|
if in_subset?(node, parent)
|
@@ -18,7 +21,7 @@ module DeepCover
|
|
18
21
|
end
|
19
22
|
|
20
23
|
def in_subset?(node, _parent)
|
21
|
-
self.class::SUBSET_CLASSES.any?{|klass| node.is_a?(klass)}
|
24
|
+
self.class::SUBSET_CLASSES.any? { |klass| node.is_a?(klass) }
|
22
25
|
end
|
23
26
|
end
|
24
27
|
end
|
data/lib/deep_cover/auto_run.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'weakref'
|
2
4
|
|
3
5
|
module DeepCover
|
@@ -10,7 +12,7 @@ module DeepCover
|
|
10
12
|
ext = File.extname(path)
|
11
13
|
# We don't care about .so files
|
12
14
|
return if ext == '.so'
|
13
|
-
path
|
15
|
+
path += '.rb' if ext != '.rb'
|
14
16
|
|
15
17
|
pairs = @autoloaded_paths[path] ||= []
|
16
18
|
pairs << [WeakRef.new(const), name]
|
@@ -21,7 +23,7 @@ module DeepCover
|
|
21
23
|
|
22
24
|
paths.flat_map do |path|
|
23
25
|
pairs = @autoloaded_paths[path] || []
|
24
|
-
pairs = pairs.map{|weak_const, name| [self.class.value_from_weak_ref(weak_const), name] }
|
26
|
+
pairs = pairs.map { |weak_const, name| [self.class.value_from_weak_ref(weak_const), name] }
|
25
27
|
pairs.select!(&:first)
|
26
28
|
pairs
|
27
29
|
end
|
@@ -49,9 +51,10 @@ module DeepCover
|
|
49
51
|
|
50
52
|
def initialize_autoloaded_paths
|
51
53
|
@autoloaded_paths = {}
|
54
|
+
# This is only used on MRI, so ObjectSpace is alright.
|
52
55
|
ObjectSpace.each_object(Module) do |mod|
|
53
56
|
mod.constants.each do |name|
|
54
|
-
if path = mod.autoload?(name)
|
57
|
+
if (path = mod.autoload?(name))
|
55
58
|
add(mod, name, path)
|
56
59
|
end
|
57
60
|
end
|
data/lib/deep_cover/backports.rb
CHANGED
data/lib/deep_cover/base.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DeepCover
|
2
4
|
module Base
|
3
5
|
def start
|
@@ -35,12 +37,19 @@ module DeepCover
|
|
35
37
|
stop
|
36
38
|
end
|
37
39
|
|
40
|
+
def config_changed(what)
|
41
|
+
if what == :paths
|
42
|
+
warn "Changing DeepCover's paths after starting coverage is highly discouraged" if @started
|
43
|
+
@custom_requirer = nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
38
47
|
def coverage
|
39
48
|
@coverage ||= Coverage.new
|
40
49
|
end
|
41
50
|
|
42
51
|
def custom_requirer
|
43
|
-
@custom_requirer ||= CustomRequirer.new
|
52
|
+
@custom_requirer ||= CustomRequirer.new(lookup_paths: config.paths)
|
44
53
|
end
|
45
54
|
|
46
55
|
def autoload_tracker
|
@@ -49,7 +58,7 @@ module DeepCover
|
|
49
58
|
|
50
59
|
def handle_relative_filename(filename)
|
51
60
|
unless Pathname.new(filename).absolute?
|
52
|
-
relative_to = File.dirname(caller
|
61
|
+
relative_to = File.dirname(caller(2..2).first.partition(/\.rb:\d/).first)
|
53
62
|
filename = File.absolute_path(filename, relative_to)
|
54
63
|
end
|
55
64
|
filename += '.rb' unless filename =~ /\.rb$/
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DeepCover
|
2
4
|
module CLI
|
3
5
|
class Debugger
|
@@ -6,15 +8,15 @@ module DeepCover
|
|
6
8
|
module ColorAST
|
7
9
|
def fancy_type
|
8
10
|
color = case
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
11
|
+
when !executable?
|
12
|
+
:faint
|
13
|
+
when !was_executed?
|
14
|
+
:red
|
15
|
+
when flow_interrupt_count > 0
|
16
|
+
:yellow
|
17
|
+
else
|
18
|
+
:green
|
19
|
+
end
|
18
20
|
Term::ANSIColor.send(color, super)
|
19
21
|
end
|
20
22
|
end
|
@@ -29,46 +31,51 @@ module DeepCover
|
|
29
31
|
end
|
30
32
|
|
31
33
|
def show
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
Tools.profile(options[:profile]) do
|
35
|
+
execute
|
36
|
+
covered_code.freeze # Our output relies on the counts, so better freeze. See [#13]
|
37
|
+
if @debug
|
38
|
+
show_line_coverage
|
39
|
+
show_instrumented_code
|
40
|
+
show_ast
|
41
|
+
end
|
42
|
+
show_char_coverage
|
37
43
|
end
|
38
|
-
show_char_coverage
|
39
44
|
pry if @debug
|
40
45
|
finish
|
41
46
|
end
|
42
47
|
|
43
48
|
def show_line_coverage
|
44
|
-
|
49
|
+
output { "Line Coverage: Builtin | DeepCover | DeepCover Strict:\n" }
|
45
50
|
begin
|
46
51
|
builtin_line_coverage = builtin_coverage(@source, @filename, @lineno)
|
47
52
|
our_line_coverage = our_coverage(@source, @filename, @lineno, **options)
|
48
53
|
our_strict_line_coverage = our_coverage(@source, @filename, @lineno, allow_partial: false, **options)
|
49
|
-
|
50
|
-
|
54
|
+
output do
|
55
|
+
lines = format(builtin_line_coverage, our_line_coverage, our_strict_line_coverage, source: @source)
|
56
|
+
number_lines(lines, lineno: @lineno)
|
57
|
+
end
|
51
58
|
rescue Exception => e
|
52
|
-
|
59
|
+
output { "Can't run coverage: #{e.class.name}: #{e}\n#{e.backtrace.join("\n")}" }
|
53
60
|
@failed = true
|
54
61
|
end
|
55
62
|
end
|
56
63
|
|
57
64
|
def show_instrumented_code
|
58
|
-
|
59
|
-
|
65
|
+
output { "\nInstrumented code:\n" }
|
66
|
+
output { format_generated_code(covered_code) }
|
60
67
|
end
|
61
68
|
|
62
69
|
def show_ast
|
63
|
-
|
70
|
+
output { "\nParsed code:\n" }
|
64
71
|
Node.prepend ColorAST
|
65
|
-
|
72
|
+
output { covered_code.covered_ast }
|
66
73
|
end
|
67
74
|
|
68
75
|
def show_char_coverage
|
69
|
-
|
76
|
+
output { "\nChar coverage:\n" }
|
70
77
|
|
71
|
-
|
78
|
+
output { format_char_cover(covered_code, show_whitespace: !!ENV['W'], **options) }
|
72
79
|
end
|
73
80
|
|
74
81
|
def pry
|
@@ -86,11 +93,29 @@ module DeepCover
|
|
86
93
|
end
|
87
94
|
|
88
95
|
def execute
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
96
|
+
execute_sample(covered_code)
|
97
|
+
# output { trace_counts } # Keep for low-level debugging purposes
|
98
|
+
rescue Exception => e
|
99
|
+
output { "Can't `execute_sample`:#{e.class.name}: #{e}\n#{e.backtrace.join("\n")}" }
|
100
|
+
@failed = true
|
101
|
+
end
|
102
|
+
|
103
|
+
def trace_counts
|
104
|
+
all = []
|
105
|
+
trace = TracePoint.new(:call) do |tr|
|
106
|
+
if %i[flow_entry_count flow_completion_count execution_count].include? tr.method_id
|
107
|
+
node = tr.self
|
108
|
+
str = "#{node.type} #{(node.value if node.respond_to?(:value))} #{tr.method_id}"
|
109
|
+
all << str unless all.last == str
|
110
|
+
end
|
111
|
+
end
|
112
|
+
trace.enable { covered_code.freeze }
|
113
|
+
all
|
114
|
+
end
|
115
|
+
|
116
|
+
def output
|
117
|
+
Tools.dont_profile do
|
118
|
+
puts yield
|
94
119
|
end
|
95
120
|
end
|
96
121
|
end
|