deep-cover 0.1.1

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