deep-cover 0.5.2 → 0.5.3

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.
Files changed (84) hide show
  1. checksums.yaml +5 -5
  2. data/.deep_cover.rb +8 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +15 -1
  5. data/.travis.yml +1 -0
  6. data/README.md +30 -1
  7. data/Rakefile +10 -1
  8. data/bin/cov +1 -1
  9. data/deep_cover.gemspec +4 -5
  10. data/exe/deep-cover +5 -3
  11. data/lib/deep_cover.rb +1 -1
  12. data/lib/deep_cover/analyser/node.rb +1 -1
  13. data/lib/deep_cover/analyser/ruby25_like_branch.rb +209 -0
  14. data/lib/deep_cover/auto_run.rb +19 -19
  15. data/lib/deep_cover/autoload_tracker.rb +181 -44
  16. data/lib/deep_cover/backports.rb +3 -1
  17. data/lib/deep_cover/base.rb +13 -8
  18. data/lib/deep_cover/basics.rb +1 -1
  19. data/lib/deep_cover/cli/debugger.rb +2 -2
  20. data/lib/deep_cover/cli/instrumented_clone_reporter.rb +21 -8
  21. data/lib/deep_cover/cli/runner.rb +126 -0
  22. data/lib/deep_cover/config_setter.rb +1 -0
  23. data/lib/deep_cover/core_ext/autoload_overrides.rb +82 -14
  24. data/lib/deep_cover/core_ext/coverage_replacement.rb +34 -5
  25. data/lib/deep_cover/core_ext/exec_callbacks.rb +27 -0
  26. data/lib/deep_cover/core_ext/load_overrides.rb +4 -6
  27. data/lib/deep_cover/core_ext/require_overrides.rb +1 -3
  28. data/lib/deep_cover/coverage.rb +105 -2
  29. data/lib/deep_cover/coverage/analysis.rb +30 -28
  30. data/lib/deep_cover/coverage/persistence.rb +60 -70
  31. data/lib/deep_cover/covered_code.rb +16 -49
  32. data/lib/deep_cover/custom_requirer.rb +112 -51
  33. data/lib/deep_cover/load.rb +10 -6
  34. data/lib/deep_cover/memoize.rb +1 -3
  35. data/lib/deep_cover/module_override.rb +7 -0
  36. data/lib/deep_cover/node/assignments.rb +2 -1
  37. data/lib/deep_cover/node/base.rb +6 -6
  38. data/lib/deep_cover/node/block.rb +10 -8
  39. data/lib/deep_cover/node/case.rb +3 -3
  40. data/lib/deep_cover/node/collections.rb +8 -0
  41. data/lib/deep_cover/node/if.rb +19 -3
  42. data/lib/deep_cover/node/literals.rb +28 -7
  43. data/lib/deep_cover/node/mixin/can_augment_children.rb +4 -4
  44. data/lib/deep_cover/node/mixin/child_can_be_empty.rb +1 -1
  45. data/lib/deep_cover/node/mixin/filters.rb +6 -2
  46. data/lib/deep_cover/node/mixin/has_child.rb +8 -8
  47. data/lib/deep_cover/node/mixin/has_child_handler.rb +3 -3
  48. data/lib/deep_cover/node/mixin/has_tracker.rb +7 -3
  49. data/lib/deep_cover/node/root.rb +1 -1
  50. data/lib/deep_cover/node/send.rb +53 -7
  51. data/lib/deep_cover/node/short_circuit.rb +11 -3
  52. data/lib/deep_cover/parser_ext/range.rb +11 -27
  53. data/lib/deep_cover/problem_with_diagnostic.rb +1 -1
  54. data/lib/deep_cover/reporter.rb +0 -1
  55. data/lib/deep_cover/reporter/base.rb +68 -0
  56. data/lib/deep_cover/reporter/html.rb +1 -1
  57. data/lib/deep_cover/reporter/html/index.rb +4 -8
  58. data/lib/deep_cover/reporter/html/site.rb +10 -18
  59. data/lib/deep_cover/reporter/html/source.rb +3 -3
  60. data/lib/deep_cover/reporter/html/template/source.html.erb +1 -1
  61. data/lib/deep_cover/reporter/istanbul.rb +86 -56
  62. data/lib/deep_cover/reporter/text.rb +5 -13
  63. data/lib/deep_cover/reporter/{util/tree.rb → tree/util.rb} +19 -21
  64. data/lib/deep_cover/tools/blank.rb +25 -0
  65. data/lib/deep_cover/tools/builtin_coverage.rb +8 -8
  66. data/lib/deep_cover/tools/dump_covered_code.rb +2 -9
  67. data/lib/deep_cover/tools/execute_sample.rb +17 -6
  68. data/lib/deep_cover/tools/format_generated_code.rb +1 -1
  69. data/lib/deep_cover/tools/indent_string.rb +26 -0
  70. data/lib/deep_cover/tools/our_coverage.rb +2 -2
  71. data/lib/deep_cover/tools/strip_heredoc.rb +18 -0
  72. data/lib/deep_cover/tracker_bucket.rb +50 -0
  73. data/lib/deep_cover/tracker_hits_per_path.rb +35 -0
  74. data/lib/deep_cover/tracker_storage.rb +76 -0
  75. data/lib/deep_cover/tracker_storage_per_path.rb +34 -0
  76. data/lib/deep_cover/version.rb +1 -1
  77. data/lib/deep_cover_entry.rb +3 -0
  78. metadata +30 -37
  79. data/bin/gemcov +0 -8
  80. data/bin/selfcov +0 -21
  81. data/lib/deep_cover/cli/deep_cover.rb +0 -126
  82. data/lib/deep_cover/coverage/base.rb +0 -81
  83. data/lib/deep_cover/coverage/istanbul.rb +0 -34
  84. data/lib/deep_cover/tools/transform_keys.rb +0 -9
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'const'
4
4
  require_relative 'literals'
5
+ require_relative 'keywords'
5
6
 
6
7
  module DeepCover
7
8
  class Node
@@ -18,7 +19,7 @@ module DeepCover
18
19
  Cvasgn = Gvasgn = Ivasgn = Lvasgn = VariableAssignment
19
20
 
20
21
  class Casgn < Node
21
- has_child cbase: [Cbase, Const, nil, Self]
22
+ has_child cbase: [Cbase, Const, nil, Self, Begin, Kwbegin]
22
23
  has_child var_name: Symbol
23
24
  has_child value: [Node, nil]
24
25
 
@@ -52,18 +52,18 @@ module DeepCover
52
52
  end
53
53
 
54
54
  # Shortcut to access children
55
- def [](v)
56
- if v.is_a?(Integer)
57
- children.fetch(v)
55
+ def [](lookup)
56
+ if lookup.is_a?(Integer)
57
+ children.fetch(lookup)
58
58
  else
59
- found = find_all(v)
59
+ found = find_all(lookup)
60
60
  case found.size
61
61
  when 1
62
62
  found.first
63
63
  when 0
64
- raise "No children of type #{v}"
64
+ raise "No children of type #{lookup}"
65
65
  else
66
- raise "Ambiguous lookup #{v}, found #{found}."
66
+ raise "Ambiguous lookup #{lookup}, found #{found}."
67
67
  end
68
68
  end
69
69
  end
@@ -21,11 +21,6 @@ module DeepCover
21
21
  include WithBlock
22
22
  end
23
23
 
24
- class CsendWithBlock < Csend
25
- include WithBlock
26
- refine_child actual_send: {safe_send: SendWithBlock}
27
- end
28
-
29
24
  class SuperWithBlock < Node
30
25
  include WithBlock
31
26
  has_extra_children arguments: Node
@@ -34,9 +29,7 @@ module DeepCover
34
29
  class Block < Node
35
30
  check_completion
36
31
  has_tracker :body
37
- has_child call: {send: SendWithBlock, csend: CsendWithBlock,
38
- zsuper: SuperWithBlock, super: SuperWithBlock,
39
- }
32
+ has_child call: {send: SendWithBlock, zsuper: SuperWithBlock, super: SuperWithBlock, csend: Csend}
40
33
  has_child args: Args
41
34
  has_child body: Node,
42
35
  can_be_empty: -> { base_node.loc.end.begin },
@@ -48,6 +41,15 @@ module DeepCover
48
41
  def children_nodes_in_flow_order
49
42
  [call, args] # Similarly to a def, the body is actually not part of the flow of this node...
50
43
  end
44
+
45
+ alias_method :rewrite_for_completion, :rewrite
46
+ def rewrite
47
+ if call.is_a?(Csend)
48
+ rewrite_for_completion.gsub('%{node}', Csend::REWRITE_SUFFIX)
49
+ else
50
+ rewrite_for_completion
51
+ end
52
+ end
51
53
  end
52
54
 
53
55
  # &foo
@@ -89,10 +89,10 @@ module DeepCover
89
89
  whens.map(&:body) << self.else
90
90
  end
91
91
 
92
- def branches_summary(of = branches)
92
+ def branches_summary(of_branches = branches)
93
93
  texts = []
94
- n = of.size
95
- if of.include? self.else
94
+ n = of_branches.size
95
+ if of_branches.include? self.else
96
96
  texts << "#{'implicit ' unless has_else?}else"
97
97
  n -= 1
98
98
  end
@@ -4,7 +4,14 @@ require_relative 'splat'
4
4
 
5
5
  module DeepCover
6
6
  class Node
7
+ module SimpleIfEmpty
8
+ def simple_literal?
9
+ children.empty?
10
+ end
11
+ end
12
+
7
13
  class Array < Node
14
+ include SimpleIfEmpty
8
15
  has_extra_children elements: Node
9
16
  executed_loc_keys :begin, :end
10
17
  end
@@ -16,6 +23,7 @@ module DeepCover
16
23
  end
17
24
 
18
25
  class Hash < Node
26
+ include SimpleIfEmpty
19
27
  has_extra_children elements: [Pair, Kwsplat]
20
28
  executed_loc_keys :begin, :end
21
29
  end
@@ -20,7 +20,7 @@ module DeepCover
20
20
 
21
21
  def child_can_be_empty(child, name)
22
22
  return false if name == :condition || style == :ternary
23
- if (name == :true_branch) == (style == :if)
23
+ if (name == :true_branch) == [:if, :elsif].include?(style)
24
24
  (base_node.loc.begin || base_node.children[0].loc.expression.succ).end
25
25
  elsif has_else?
26
26
  base_node.loc.else.end.succ
@@ -33,8 +33,8 @@ module DeepCover
33
33
  [true_branch, false_branch]
34
34
  end
35
35
 
36
- def branches_summary(of = branches)
37
- of.map do |jump|
36
+ def branches_summary(of_branches = branches)
37
+ of_branches.map do |jump|
38
38
  "#{'implicit ' if jump.is_a?(EmptyBody) && !has_else?}#{jump == false_branch ? 'falsy' : 'truthy'} branch"
39
39
  end.join(' and ')
40
40
  end
@@ -49,6 +49,22 @@ module DeepCover
49
49
  keyword ? keyword.source.to_sym : :ternary
50
50
  end
51
51
 
52
+ def root_if_node
53
+ if style != :elsif
54
+ self
55
+ else
56
+ parent.root_if_node
57
+ end
58
+ end
59
+
60
+ def deepest_elsif_node
61
+ return if style != :elsif
62
+ return self if loc_hash[:else] && loc_hash[:else].source == 'else'
63
+ return self if false_branch.is_a?(EmptyBody)
64
+ false_branch.deepest_elsif_node
65
+ end
66
+
67
+
52
68
  def has_else?
53
69
  !!base_node.loc.to_hash[:else]
54
70
  end
@@ -2,19 +2,30 @@
2
2
 
3
3
  require_relative 'begin'
4
4
  require_relative 'variables'
5
+ require_relative 'collections'
5
6
  module DeepCover
6
7
  class Node
7
- # Singletons
8
- class SingletonLiteral < Node
8
+ def simple_literal?
9
+ false
10
+ end
11
+
12
+ class StaticLiteral < Node
9
13
  executed_loc_keys :expression
14
+
15
+ def simple_literal?
16
+ true
17
+ end
18
+ end
19
+
20
+ # Singletons
21
+ class SingletonLiteral < StaticLiteral
10
22
  end
11
23
  True = False = Nil = Self = SingletonLiteral
12
24
 
13
25
  # Atoms
14
26
  def self.atom(type)
15
- ::Class.new(Node) do
27
+ ::Class.new(StaticLiteral) do
16
28
  has_child value: type
17
- executed_loc_keys :expression
18
29
  end
19
30
  end
20
31
  Sym = atom(::Symbol)
@@ -22,12 +33,11 @@ module DeepCover
22
33
  Float = atom(::Float)
23
34
  Complex = atom(::Complex)
24
35
  Rational = atom(::Rational)
25
- class Regopt < Node
36
+ class Regopt < StaticLiteral
26
37
  has_extra_children options: [::Symbol]
27
- executed_loc_keys :expression
28
38
  end
29
39
 
30
- class Str < Node
40
+ class Str < StaticLiteral
31
41
  has_child value: ::String
32
42
 
33
43
  def executed_loc_keys
@@ -43,8 +53,17 @@ module DeepCover
43
53
  end
44
54
  end
45
55
 
56
+ # (Potentially) dynamic
57
+ module SimpleIfItsChildrenAre
58
+ def simple_literal?
59
+ children.all?(&:simple_literal?)
60
+ end
61
+ end
62
+
46
63
  # Di-atomic
47
64
  class Range < Node
65
+ include SimpleIfItsChildrenAre
66
+
48
67
  has_child from: Node
49
68
  has_child to: Node
50
69
  end
@@ -67,6 +86,8 @@ module DeepCover
67
86
  DynamicLiteral.has_evaluated_segments
68
87
 
69
88
  class Regexp < Node
89
+ include SimpleIfItsChildrenAre
90
+
70
91
  has_evaluated_segments
71
92
  has_child option: Regopt
72
93
  end
@@ -41,12 +41,12 @@ module DeepCover
41
41
  # same as:
42
42
  # has_child foo: [NodeClass, ...], remap: {type: NodeClass, ...}
43
43
  #
44
- def has_child(remap: nil, **h)
45
- name, types = h.first
44
+ def has_child(remap: nil, **args)
45
+ name, types = args.first
46
46
  if types.is_a? Hash
47
47
  raise 'Use either remap or a hash as type but not both' if remap
48
48
  remap = types
49
- h[name] = types = []
49
+ args[name] = types = []
50
50
  end
51
51
  if remap.is_a? Hash
52
52
  type_map = remap
@@ -57,7 +57,7 @@ module DeepCover
57
57
  end
58
58
  types.concat(type_map.values).uniq!
59
59
  end
60
- super(**h, remap: remap)
60
+ super(**args, remap: remap)
61
61
  end
62
62
  end
63
63
  end
@@ -10,7 +10,7 @@ module DeepCover
10
10
  end
11
11
  end
12
12
 
13
- def remap_child(child, name = raise)
13
+ def remap_child(child, name)
14
14
  if child == nil
15
15
  if (ChildCanBeEmpty.last_empty_position = child_can_be_empty(child, name))
16
16
  return Node::EmptyBody
@@ -26,8 +26,12 @@ module DeepCover
26
26
  is_a?(Node::Send) && RAISING_MESSAGES.include?(message) && receiver == nil
27
27
  end
28
28
 
29
+ def is_warn?
30
+ is_a?(Node::Send) && message == :warn
31
+ end
32
+
29
33
  def is_default_argument?
30
- parent.is_a?(Node::Optarg)
34
+ parent.is_a?(Node::Optarg) && simple_literal?
31
35
  end
32
36
 
33
37
  def is_case_implicit_else?
@@ -36,7 +40,7 @@ module DeepCover
36
40
 
37
41
  def is_trivial_if?
38
42
  # Supports only node being a branch or the fork itself
39
- parent.is_a?(Node::If) && parent.condition.is_a?(Node::SingletonLiteral)
43
+ parent.is_a?(Node::If) && parent.condition.simple_literal?
40
44
  end
41
45
  end
42
46
  end
@@ -22,9 +22,9 @@ module DeepCover
22
22
  end
23
23
 
24
24
  module ClassMethods
25
- def has_child(rest_: false, refine_: false, **h)
26
- raise "Needs exactly one custom named argument, got #{h.size}" if h.size != 1
27
- name, types = h.first
25
+ def has_child(rest_: false, refine_: false, **args)
26
+ raise "Needs exactly one custom named argument, got #{args.size}" if args.size != 1
27
+ name, types = args.first
28
28
  raise TypeError, "Expect a Symbol for name, got a #{name.class} (#{name.inspect})" unless name.is_a?(Symbol)
29
29
  update_children_const(name, rest: rest_) unless refine_
30
30
  define_accessor(name) unless refine_
@@ -32,15 +32,15 @@ module DeepCover
32
32
  self
33
33
  end
34
34
 
35
- def has_extra_children(**h)
36
- has_child(**h, rest_: true)
35
+ def has_extra_children(**args)
36
+ has_child(**args, rest_: true)
37
37
  end
38
38
 
39
- def refine_child(child_name = nil, **h)
39
+ def refine_child(child_name = nil, **args)
40
40
  if child_name
41
- h = {child_name => self::CHILDREN_TYPES.fetch(child_name), **h}
41
+ args = {child_name => self::CHILDREN_TYPES.fetch(child_name), **args}
42
42
  end
43
- has_child(**h, refine_: true)
43
+ has_child(**args, refine_: true)
44
44
  end
45
45
 
46
46
  def child_index_to_name(index, nb_children)
@@ -30,11 +30,11 @@ module DeepCover
30
30
  class_eval <<-EVAL, __FILE__, __LINE__ + 1
31
31
  module #{const_name} # module RewriteHandler
32
32
  module ClassMethods # module ClassMethods
33
- def has_child(#{action}: nil, **h) # def has_child(rewrite: nil, **h)
34
- name, _types = h.first # name, _types = h.first
33
+ def has_child(#{action}: nil, **args) # def has_child(rewrite: nil, **args)
34
+ name, _types = args.first # name, _types = args.first
35
35
  define_child_handler(#{template.inspect}, # define_child_handler('rewrite_%{child}',
36
36
  name, #{action}) # name, rewrite)
37
- super(**h) # super(**h)
37
+ super(**args) # super(**args)
38
38
  end # end
39
39
  end # end
40
40
 
@@ -9,10 +9,14 @@ module DeepCover
9
9
  TRACKERS = {}
10
10
 
11
11
  def initialize(*)
12
- @tracker_offset = covered_code.allocate_trackers(self.class::TRACKERS.size).begin
12
+ @tracker_offset = tracker_storage.allocate_trackers(self.class::TRACKERS.size).begin
13
13
  super
14
14
  end
15
15
 
16
+ def tracker_storage
17
+ covered_code.tracker_storage
18
+ end
19
+
16
20
  def tracker_sources
17
21
  self.class::TRACKERS.map do |name, _|
18
22
  [:"#{name}_tracker", send(:"#{name}_tracker_source")]
@@ -29,10 +33,10 @@ module DeepCover
29
33
  i = self::TRACKERS[name] = self::TRACKERS.size
30
34
  class_eval <<-EVAL, __FILE__, __LINE__ + 1
31
35
  def #{name}_tracker_source
32
- covered_code.tracker_source(@tracker_offset + #{i})
36
+ tracker_storage.tracker_source(@tracker_offset + #{i})
33
37
  end
34
38
  def #{name}_tracker_hits
35
- covered_code.tracker_hits(@tracker_offset + #{i})
39
+ tracker_storage[@tracker_offset + #{i}]
36
40
  end
37
41
  EVAL
38
42
  end
@@ -7,7 +7,7 @@ module DeepCover
7
7
  can_be_empty: -> { Parser::Source::Range.new(covered_code.buffer, 0, 0) },
8
8
  is_statement: true,
9
9
  rewrite: -> {
10
- "#{covered_code.trackers_setup_source};%{root_tracker};%{local}=nil;%{node}"
10
+ "#{tracker_storage.setup_source};%{root_tracker};%{local}=nil;%{node}"
11
11
  }
12
12
  attr_reader :covered_code
13
13
  alias_method :flow_entry_count, :root_tracker_hits
@@ -67,16 +67,54 @@ module DeepCover
67
67
  check_completion
68
68
  end
69
69
 
70
+ class CsendInnerSend < SendBase
71
+ has_tracker :completion
72
+ include ExecutedAfterChildren
73
+
74
+ def has_block?
75
+ parent.parent.is_a?(Block)
76
+ end
77
+
78
+ def rewrite
79
+ # All the rest of the rewriting logic is in Csend
80
+ '%{node};%{completion_tracker};' unless has_block?
81
+ end
82
+
83
+ def flow_completion_count
84
+ return parent.parent.flow_completion_count if has_block?
85
+ completion_tracker_hits
86
+ end
87
+
88
+ def loc_hash
89
+ # This is only a partial Send, the receiver param and the dot are actually handled by the parent Csend.
90
+ h = super.dup
91
+ h[:expression] = h[:expression].with(begin_pos: h[:selector_begin].begin_pos)
92
+ h
93
+ end
94
+ end
95
+
70
96
  class Csend < Node
97
+ # The overall rewriting goal is this:
98
+ # temp = *receiver*;
99
+ # if nil != temp
100
+ # TRACK_my_NOT_NIL
101
+ # temp = temp&.*actual_send*{block}
102
+ # TRACK_actual_send_COMPLETION
103
+ # t
104
+ # else
105
+ # nil
106
+ # end
107
+ # This is split across the children and the CsendInnerSend
71
108
  include Branch
72
- has_tracker :conditional
109
+ has_tracker :not_nil
73
110
  has_child receiver: Node,
74
- rewrite: '(%{local}=%{node};%{conditional_tracker} if %{local} != nil;%{local})'
111
+ rewrite: '(%{local}=%{node};if nil != %{local};%{not_nil_tracker};%{local}=%{local}'
112
+ REWRITE_SUFFIX = '%{node};%{local};else;nil;end)'
75
113
 
76
- has_child actual_send: {safe_send: Send},
77
- flow_entry_count: :conditional_tracker_hits
114
+ has_child actual_send: {safe_send: CsendInnerSend},
115
+ flow_entry_count: :not_nil_tracker_hits
78
116
 
79
- def initialize(base_node, base_children: base_node.children, **)
117
+ def initialize(base_node, base_children: base_node.children, **) # rubocop:disable Naming/UncommunicativeMethodParamName [#5436]
80
118
  send_without_receiver = base_node.updated(:safe_send, [nil, *base_node.children.drop(1)])
81
119
  base_children = [base_children.first, send_without_receiver]
82
120
  super
@@ -84,6 +122,14 @@ module DeepCover
84
122
 
85
123
  executed_loc_keys :dot
86
124
 
125
+ def has_block?
126
+ parent.is_a?(Block)
127
+ end
128
+
129
+ def rewrite
130
+ REWRITE_SUFFIX unless has_block?
131
+ end
132
+
87
133
  def execution_count
88
134
  receiver.flow_completion_count
89
135
  end
@@ -98,8 +144,8 @@ module DeepCover
98
144
  ]
99
145
  end
100
146
 
101
- def branches_summary(of = branches)
102
- of.map do |jump|
147
+ def branches_summary(of_branches = branches)
148
+ of_branches.map do |jump|
103
149
  jump == actual_send ? 'safe send' : 'nil shortcut'
104
150
  end.join(' and ')
105
151
  end