deep-cover 0.1.1

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 (99) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +10 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +8 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +127 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/cov +43 -0
  12. data/bin/gemcov +8 -0
  13. data/bin/selfcov +21 -0
  14. data/bin/setup +8 -0
  15. data/bin/testall +88 -0
  16. data/deep_cover.gemspec +44 -0
  17. data/exe/deep-cover +6 -0
  18. data/future_read_me.md +108 -0
  19. data/lib/deep-cover.rb +1 -0
  20. data/lib/deep_cover.rb +11 -0
  21. data/lib/deep_cover/analyser.rb +24 -0
  22. data/lib/deep_cover/analyser/base.rb +51 -0
  23. data/lib/deep_cover/analyser/branch.rb +20 -0
  24. data/lib/deep_cover/analyser/covered_code_source.rb +31 -0
  25. data/lib/deep_cover/analyser/function.rb +12 -0
  26. data/lib/deep_cover/analyser/ignore_uncovered.rb +19 -0
  27. data/lib/deep_cover/analyser/node.rb +11 -0
  28. data/lib/deep_cover/analyser/per_char.rb +20 -0
  29. data/lib/deep_cover/analyser/per_line.rb +23 -0
  30. data/lib/deep_cover/analyser/statement.rb +31 -0
  31. data/lib/deep_cover/analyser/subset.rb +24 -0
  32. data/lib/deep_cover/auto_run.rb +49 -0
  33. data/lib/deep_cover/autoload_tracker.rb +75 -0
  34. data/lib/deep_cover/backports.rb +9 -0
  35. data/lib/deep_cover/base.rb +55 -0
  36. data/lib/deep_cover/builtin_takeover.rb +2 -0
  37. data/lib/deep_cover/cli/debugger.rb +93 -0
  38. data/lib/deep_cover/cli/deep_cover.rb +49 -0
  39. data/lib/deep_cover/cli/instrumented_clone_reporter.rb +105 -0
  40. data/lib/deep_cover/config.rb +52 -0
  41. data/lib/deep_cover/core_ext/autoload_overrides.rb +40 -0
  42. data/lib/deep_cover/core_ext/coverage_replacement.rb +26 -0
  43. data/lib/deep_cover/core_ext/load_overrides.rb +24 -0
  44. data/lib/deep_cover/core_ext/require_overrides.rb +36 -0
  45. data/lib/deep_cover/coverage.rb +198 -0
  46. data/lib/deep_cover/covered_code.rb +138 -0
  47. data/lib/deep_cover/custom_requirer.rb +93 -0
  48. data/lib/deep_cover/node.rb +8 -0
  49. data/lib/deep_cover/node/arguments.rb +50 -0
  50. data/lib/deep_cover/node/assignments.rb +250 -0
  51. data/lib/deep_cover/node/base.rb +99 -0
  52. data/lib/deep_cover/node/begin.rb +25 -0
  53. data/lib/deep_cover/node/block.rb +53 -0
  54. data/lib/deep_cover/node/boolean.rb +22 -0
  55. data/lib/deep_cover/node/branch.rb +28 -0
  56. data/lib/deep_cover/node/case.rb +94 -0
  57. data/lib/deep_cover/node/collections.rb +21 -0
  58. data/lib/deep_cover/node/const.rb +10 -0
  59. data/lib/deep_cover/node/def.rb +38 -0
  60. data/lib/deep_cover/node/empty_body.rb +21 -0
  61. data/lib/deep_cover/node/exceptions.rb +74 -0
  62. data/lib/deep_cover/node/if.rb +36 -0
  63. data/lib/deep_cover/node/keywords.rb +84 -0
  64. data/lib/deep_cover/node/literals.rb +77 -0
  65. data/lib/deep_cover/node/loops.rb +72 -0
  66. data/lib/deep_cover/node/mixin/can_augment_children.rb +65 -0
  67. data/lib/deep_cover/node/mixin/check_completion.rb +16 -0
  68. data/lib/deep_cover/node/mixin/child_can_be_empty.rb +25 -0
  69. data/lib/deep_cover/node/mixin/executed_after_children.rb +13 -0
  70. data/lib/deep_cover/node/mixin/execution_location.rb +56 -0
  71. data/lib/deep_cover/node/mixin/flow_accounting.rb +63 -0
  72. data/lib/deep_cover/node/mixin/has_child.rb +138 -0
  73. data/lib/deep_cover/node/mixin/has_child_handler.rb +73 -0
  74. data/lib/deep_cover/node/mixin/has_tracker.rb +44 -0
  75. data/lib/deep_cover/node/mixin/is_statement.rb +18 -0
  76. data/lib/deep_cover/node/mixin/rewriting.rb +32 -0
  77. data/lib/deep_cover/node/mixin/wrapper.rb +13 -0
  78. data/lib/deep_cover/node/module.rb +64 -0
  79. data/lib/deep_cover/node/root.rb +18 -0
  80. data/lib/deep_cover/node/send.rb +83 -0
  81. data/lib/deep_cover/node/splat.rb +13 -0
  82. data/lib/deep_cover/node/variables.rb +14 -0
  83. data/lib/deep_cover/parser_ext/range.rb +40 -0
  84. data/lib/deep_cover/reporter.rb +6 -0
  85. data/lib/deep_cover/reporter/istanbul.rb +151 -0
  86. data/lib/deep_cover/tools.rb +18 -0
  87. data/lib/deep_cover/tools/builtin_coverage.rb +50 -0
  88. data/lib/deep_cover/tools/camelize.rb +8 -0
  89. data/lib/deep_cover/tools/dump_covered_code.rb +32 -0
  90. data/lib/deep_cover/tools/execute_sample.rb +23 -0
  91. data/lib/deep_cover/tools/format.rb +16 -0
  92. data/lib/deep_cover/tools/format_char_cover.rb +18 -0
  93. data/lib/deep_cover/tools/format_generated_code.rb +25 -0
  94. data/lib/deep_cover/tools/number_lines.rb +18 -0
  95. data/lib/deep_cover/tools/our_coverage.rb +9 -0
  96. data/lib/deep_cover/tools/require_relative_dir.rb +10 -0
  97. data/lib/deep_cover/tools/silence_warnings.rb +15 -0
  98. data/lib/deep_cover/version.rb +3 -0
  99. metadata +326 -0
@@ -0,0 +1,77 @@
1
+ require_relative 'begin'
2
+ require_relative 'variables'
3
+ module DeepCover
4
+ class Node
5
+ # Singletons
6
+ class SingletonLiteral < Node
7
+ executed_loc_keys :expression
8
+ end
9
+ True = False = Nil = SingletonLiteral
10
+
11
+ # Atoms
12
+ def self.atom(type)
13
+ ::Class.new(Node) do
14
+ has_child value: type
15
+ executed_loc_keys :expression
16
+ end
17
+ end
18
+ Sym = atom(::Symbol)
19
+ Int = atom(::Integer)
20
+ Float = atom(::Float)
21
+ Complex = atom(::Complex)
22
+ Rational = atom(::Rational)
23
+ class Regopt < Node
24
+ has_extra_children options: [::Symbol]
25
+ executed_loc_keys :expression
26
+ end
27
+
28
+ class Str < Node
29
+ has_child value: ::String
30
+
31
+ def executed_loc_keys
32
+ keys = [:expression, :heredoc_body, :heredoc_end]
33
+
34
+ exp = expression
35
+ keys.delete(:expression) if exp && exp.source !~ /\S/
36
+
37
+ hd_body = loc_hash[:heredoc_body]
38
+ keys.delete(:heredoc_body) if hd_body && hd_body.source !~ /\S/
39
+
40
+ keys
41
+ end
42
+ end
43
+
44
+ # Di-atomic
45
+ class Range < Node
46
+ has_child from: Node
47
+ has_child to: Node
48
+ end
49
+ Erange = Irange = Range
50
+
51
+ # Dynamic
52
+ def self.has_evaluated_segments
53
+ has_extra_children constituents: [Str, Begin, Ivar, Cvar, Gvar, Dstr, NthRef]
54
+ end
55
+ class DynamicLiteral < Node
56
+ def executed_loc_keys
57
+ if loc_hash[:heredoc_end]
58
+ [:expression, :heredoc_end]
59
+ else
60
+ [:begin, :end]
61
+ end
62
+ end
63
+ end
64
+ Dsym = Dstr = DynamicLiteral
65
+ DynamicLiteral.has_evaluated_segments
66
+
67
+ class Regexp < Node
68
+ has_evaluated_segments
69
+ has_child option: Regopt
70
+ end
71
+
72
+ class Xstr < Node
73
+ check_completion
74
+ has_evaluated_segments
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,72 @@
1
+ module DeepCover
2
+ class Node
3
+ class For < Node
4
+ has_tracker :body
5
+ has_child assignments: [Mlhs, VariableAssignment], flow_entry_count: -> { body.flow_entry_count if body }
6
+ has_child iterable: [Node], flow_entry_count: -> { flow_entry_count }
7
+ has_child body: Node,
8
+ can_be_empty: -> { base_node.loc.end.begin },
9
+ flow_entry_count: :body_tracker_hits,
10
+ rewrite: '((%{body_tracker};%{local}=nil;%{node}))'
11
+ check_completion
12
+
13
+ def execution_count
14
+ iterable.flow_completion_count
15
+ end
16
+ end
17
+
18
+ class Until < Node
19
+ has_tracker :body
20
+ has_child condition: Node, rewrite: '((%{node})) || (%{body_tracker};false)'
21
+ has_child body: Node,
22
+ can_be_empty: -> { base_node.loc.end.begin },
23
+ flow_entry_count: :body_tracker_hits
24
+ check_completion
25
+
26
+ def execution_count
27
+ # TODO: while this distringuishes correctly 0 vs >0, the number return is often too high
28
+ condition.flow_completion_count
29
+ end
30
+ end
31
+
32
+ class UntilPost < Node
33
+ has_tracker :body
34
+ has_child condition: Node, rewrite: '((%{node})) || (%{body_tracker};false)'
35
+ has_child body: Kwbegin, flow_entry_count: -> { body_tracker_hits + parent.flow_entry_count }
36
+ check_completion
37
+
38
+ def execution_count
39
+ # TODO: while this distringuishes correctly 0 vs >0, the number return is often too high
40
+ body.flow_completion_count
41
+ end
42
+ # TODO: test
43
+ end
44
+
45
+ class While < Node
46
+ has_tracker :body
47
+ has_child condition: Node, rewrite: '((%{node})) && %{body_tracker}'
48
+ has_child body: Node,
49
+ can_be_empty: -> { base_node.loc.end.begin },
50
+ flow_entry_count: :body_tracker_hits
51
+ check_completion
52
+
53
+ def execution_count
54
+ # TODO: while this distringuishes correctly 0 vs >0, the number return is often too high
55
+ condition.flow_completion_count
56
+ end
57
+ end
58
+
59
+ class WhilePost < Node
60
+ has_tracker :body
61
+ has_child condition: Node, rewrite: '((%{node})) && %{body_tracker}'
62
+ has_child body: Kwbegin, flow_entry_count: -> { body_tracker_hits + parent.flow_entry_count }
63
+ check_completion
64
+
65
+ def execution_count
66
+ # TODO: while this distringuishes correctly 0 vs >0, the number return is often too high
67
+ body.flow_completion_count
68
+ end
69
+ # TODO: test
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,65 @@
1
+ module DeepCover
2
+ module Node::Mixin
3
+ module CanAugmentChildren
4
+ def self.included(base)
5
+ base.has_child_handler('remap_%{name}')
6
+ base.singleton_class.prepend ClassMethods
7
+ end
8
+
9
+ # Augment creates a covered node from the child_base_node.
10
+ # Caution: receiver is not fully constructed since it is also being augmented.
11
+ # don't call `children`
12
+ def augment_children(child_base_nodes)
13
+ missing = self.class.min_children - child_base_nodes.size
14
+ if missing > 0
15
+ child_base_nodes = [*child_base_nodes, *Array.new(missing)]
16
+ end
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) rescue binding.pry
19
+
20
+ if (klass = remap_child(child, child_name))
21
+ klass.new(child, parent: self, index: child_index)
22
+ else
23
+ child
24
+ end
25
+ end
26
+ end
27
+ private :augment_children
28
+
29
+ def remap_child(child, name=nil)
30
+ return unless child.is_a?(Parser::AST::Node)
31
+ class_name = Tools.camelize(child.type)
32
+ Node.const_defined?(class_name) ? Node.const_get(class_name) : Node
33
+ end
34
+
35
+ module ClassMethods
36
+
37
+ # This handles the following shortcuts:
38
+ # has_child foo: {type: NodeClass, ...}
39
+ # same as:
40
+ # has_child foo: [], remap: {type: NodeClass, ...}
41
+ # same as:
42
+ # has_child foo: [NodeClass, ...], remap: {type: NodeClass, ...}
43
+ #
44
+ def has_child(remap: nil, **h)
45
+ name, types = h.first
46
+ if types.is_a? Hash
47
+ raise "Use either remap or a hash as type but not both" if remap
48
+ remap = types
49
+ h[name] = types = []
50
+ end
51
+ if remap.is_a? Hash
52
+ type_map = remap
53
+ remap = -> (child) do
54
+ klass = type_map[child.type] if child.respond_to? :type
55
+ klass ||= type_map[child.class]
56
+ klass
57
+ end
58
+ types.concat(type_map.values).uniq!
59
+ end
60
+ super(**h, remap: remap)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,16 @@
1
+ require_relative 'executed_after_children'
2
+
3
+ module DeepCover
4
+ module Node::Mixin
5
+ module CheckCompletion
6
+ def check_completion(outer:'(%{node})', inner:'(%{node})')
7
+ has_tracker :completion
8
+ include ExecutedAfterChildren
9
+ alias_method :flow_completion_count, :completion_tracker_hits
10
+ pre, post = outer.split('%{node}')
11
+ # The local=local is to avoid Ruby warning about "Possible use of value in void context"
12
+ define_method(:rewrite) { "#{pre}(%{local}=#{inner};%{completion_tracker};%{local}=%{local})#{post}" }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ module DeepCover
2
+ module Node::Mixin
3
+ module ChildCanBeEmpty
4
+ class << self
5
+ attr_accessor :last_empty_position # Ugly hack to pass info from Handler to constructor
6
+ def included(base)
7
+ base.has_child_handler('%{name}_can_be_empty')
8
+ end
9
+ end
10
+
11
+ def remap_child(child, name=raise)
12
+ if child == nil
13
+ if (ChildCanBeEmpty.last_empty_position = child_can_be_empty(child, name))
14
+ return Node::EmptyBody
15
+ end
16
+ end
17
+ super
18
+ end
19
+
20
+ def child_can_be_empty(_child, _name=nil)
21
+ false
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ module DeepCover
2
+ module Node::Mixin
3
+ # By default, nodes are considered executed if they are entered.
4
+ # Some are considered executed only if their arguments complete.
5
+ module ExecutedAfterChildren
6
+ def execution_count
7
+ last = children_nodes_in_flow_order.last
8
+ return last.flow_completion_count if last
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,56 @@
1
+ module DeepCover
2
+ module Node::Mixin
3
+ module ExecutionLocation
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ base.has_child_handler('%{name}_executed_loc_keys')
7
+ end
8
+
9
+ module ClassMethods
10
+ # Macro to define the executed_loc_keys
11
+ def executed_loc_keys(*loc_keys)
12
+ # #flatten allows passing an empty array to be explicit
13
+ loc_keys = loc_keys.flatten
14
+ define_method :executed_loc_keys do
15
+ loc_keys
16
+ end
17
+ end
18
+ end
19
+
20
+ def executed_loc_keys
21
+ loc_hash.keys - [:expression]
22
+ end
23
+
24
+ def child_executed_loc_keys(_child, _child_name)
25
+ nil
26
+ end
27
+
28
+ def executed_locs
29
+ if (keys = parent.child_executed_loc_keys(self))
30
+ inherited = parent.loc_hash.values_at(*keys)
31
+ end
32
+ [ *loc_hash.values_at(*executed_loc_keys),
33
+ *inherited
34
+ ].compact
35
+ end
36
+
37
+ def loc_hash
38
+ @loc_hash ||= base_node.location.to_hash
39
+ end
40
+
41
+ def expression
42
+ loc_hash[:expression]
43
+ end
44
+
45
+ def source
46
+ expression.source if expression
47
+ end
48
+
49
+ # Returns an array of character numbers (in the original buffer) that
50
+ # pertain exclusively to this node (and thus not to any children).
51
+ def proper_range
52
+ executed_locs.map(&:to_a).inject([], :+).uniq rescue binding.pry
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,63 @@
1
+ module DeepCover
2
+ module Node::Mixin
3
+ module FlowAccounting
4
+ def self.included(base)
5
+ base.has_child_handler('%{name}_flow_entry_count')
6
+ end
7
+
8
+ # Returns true iff it is executable and if was successfully executed
9
+ def was_executed?
10
+ # There is a rare case of non executable nodes that have important data in flow_entry_count / flow_completion_count,
11
+ # like `if cond; end`, so make sure it's actually executable first...
12
+ executable? && execution_count > 0
13
+ end
14
+
15
+ # Returns the control flow entered the node.
16
+ # The control flow can then either complete normally or be interrupted
17
+ #
18
+ # Implementation: This is always the responsibility of the parent; Nodes should not override.
19
+ def flow_entry_count
20
+ parent.child_flow_entry_count(self)
21
+ end
22
+
23
+ # Returns the number of times it changed the usual control flow (e.g. raised, returned, ...)
24
+ # Implementation: This is always deduced; Nodes should not override.
25
+ def flow_interrupt_count
26
+ flow_entry_count - flow_completion_count
27
+ end
28
+
29
+ ### These are refined by subclasses
30
+
31
+ # Returns true iff it is executable. Keywords like `end` are not executable, but literals like `42` are executable.
32
+ def executable?
33
+ true
34
+ end
35
+
36
+ # Returns number of times the node itself was "executed". Definition of executed depends on the node.
37
+ def execution_count
38
+ flow_entry_count
39
+ end
40
+
41
+ # Returns the number of times the control flow succesfully left the node.
42
+ # This is the responsability of the child Node, never of the parent.
43
+ # Must be refined if the child node may have an impact on control flow (raising, branching, ...)
44
+ def flow_completion_count
45
+ last = children_nodes_in_flow_order.last
46
+ return last.flow_completion_count if last
47
+ flow_entry_count
48
+ end
49
+
50
+ # Returns the number of time the control flow entered this child_node.
51
+ # This is the responsability of the Node, not of the child.
52
+ # Must be refined if the parent node may have an impact on control flow (raising, branching, ...)
53
+ def child_flow_entry_count(child, _name = nil)
54
+ prev = child.previous_sibling
55
+ if prev
56
+ prev.flow_completion_count
57
+ else
58
+ flow_entry_count
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,138 @@
1
+ module DeepCover
2
+ module Node::Mixin
3
+ module HasChild
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ setup_constants(base)
7
+ end
8
+
9
+ def self.setup_constants(subclass)
10
+ subclass.const_set :CHILDREN, {}
11
+ subclass.const_set :CHILDREN_TYPES, {}
12
+ end
13
+
14
+
15
+ def initialize(*)
16
+ super
17
+ self.validate_children_types(children) rescue binding.pry
18
+ end
19
+
20
+ def validate_children_types(nodes)
21
+ mismatches = self.class.check_children_types(nodes)
22
+ unless mismatches.empty?
23
+ raise TypeError, "Invalid children types for #{self.class}(type: #{self.type}): #{mismatches}"
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+ def has_child(rest: false, **h)
29
+ raise "Needs exactly one custom named argument, got #{h.size}" if h.size != 1
30
+ name, types = h.first
31
+ raise TypeError, "Expect a Symbol for name, got a #{name.class} (#{name.inspect})" unless name.is_a?(Symbol)
32
+ update_children_const(name, rest: rest)
33
+ define_accessor(name)
34
+ add_runtime_check(name, types)
35
+ self
36
+ end
37
+
38
+ def has_extra_children(**h)
39
+ has_child(**h, rest: true)
40
+ end
41
+
42
+ def child_index_to_name(index, nb_children)
43
+ self::CHILDREN.each do |name, i|
44
+ return name if i == index || (i == index - nb_children) ||
45
+ (i.is_a?(Range) && i.begin <= index && i.end + nb_children >= index)
46
+ end
47
+ raise IndexError, "index #{index} does not correspond to any child of #{self}"
48
+ end
49
+
50
+ def check_children_types(nodes)
51
+ types = expected_types(nodes)
52
+ nodes_mismatches(nodes, types)
53
+ end
54
+
55
+ def min_children
56
+ self::CHILDREN.values.grep(Integer).size
57
+ end
58
+
59
+ private
60
+
61
+ def expected_types(nodes)
62
+ self::CHILDREN.flat_map do |name, i|
63
+ type = self::CHILDREN_TYPES[name]
64
+ Array.new(nodes.values_at(i).size, type)
65
+ end
66
+ end
67
+
68
+ def nodes_mismatches(nodes, types)
69
+ nodes = nodes.dup
70
+ nodes[nodes.size...types.size] = nil
71
+ nodes.zip(types).reject do |node, type|
72
+ node_matches_type?(node, type)
73
+ end
74
+ end
75
+
76
+ def node_matches_type?(node, expected)
77
+ case expected
78
+ when :any
79
+ true
80
+ when nil
81
+ node.nil?
82
+ when Array
83
+ expected.any? {|exp| node_matches_type?(node, exp) }
84
+ when Class
85
+ node.is_a?(expected)
86
+ when Symbol
87
+ node.is_a?(Node) && node.type == expected
88
+ else
89
+ raise "Unrecognized expected type #{expected}"
90
+ end
91
+ end
92
+
93
+ def inherited(subclass)
94
+ HasChild.setup_constants(subclass)
95
+ super
96
+ end
97
+
98
+ def const_missing(name)
99
+ const_set(name, self::CHILDREN.fetch(name.downcase) { return super })
100
+ end
101
+
102
+ def update_children_const(name, rest: false)
103
+ children_map = self::CHILDREN
104
+ already_has_rest = false
105
+ children_map.each do |key, value|
106
+ if value.is_a? Range
107
+ children_map[key] = children_map[key].begin..(children_map[key].end - 1)
108
+ already_has_rest = key
109
+ elsif value < 0
110
+ children_map[key] -= 1
111
+ end
112
+ end
113
+ children_map[name] = if rest
114
+ raise "Class #{self} can't have extra children '#{name}' because it already has '#{name}' (#{children_map.inspect})" if already_has_rest
115
+ children_map.size..-1
116
+ elsif already_has_rest
117
+ -1
118
+ else
119
+ children_map.size
120
+ end
121
+ end
122
+
123
+ def define_accessor(name)
124
+ warn "child name '#{name}' conflicts with existing method for #{self}" if method_defined? name
125
+ class_eval <<-end_eval, __FILE__, __LINE__
126
+ def #{name}
127
+ children[#{name.upcase}]
128
+ end
129
+ end_eval
130
+ end
131
+
132
+ def add_runtime_check(name, type)
133
+ self::CHILDREN_TYPES[name] = type
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end