deep-cover 0.5.2 → 0.5.3

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