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,93 @@
1
+ # TODO: must handle circular requires
2
+
3
+ module DeepCover
4
+ class CustomRequirer
5
+ attr_reader :load_path, :loaded_features
6
+ def initialize(load_path=$LOAD_PATH, loaded_features=$LOADED_FEATURES)
7
+ @load_path = load_path
8
+ @loaded_features = loaded_features
9
+ end
10
+
11
+ # Returns a path to an existing file or nil if none can be found.
12
+ # The search follows how ruby search for files using the $LOAD_PATH
13
+ #
14
+ # An absolute path is returned directly if it exists, otherwise nil
15
+ # is returned without searching anywhere else.
16
+ def resolve_path(path)
17
+ path = File.absolute_path(path) if path.start_with?('./') || path.start_with?('../')
18
+
19
+ if Pathname.new(path).absolute?
20
+ return path if File.exists?(path)
21
+ return nil
22
+ end
23
+
24
+ @load_path.each do |load_path|
25
+ possible_path = File.absolute_path(path, load_path)
26
+ return possible_path if File.exists?(possible_path)
27
+ end
28
+
29
+ nil
30
+ end
31
+
32
+ # Homemade #require to be able to instrument the code before it gets executed.
33
+ # Returns true when everything went right. (Same as regular ruby)
34
+ # Returns false when the found file was already required. (Same as regular ruby)
35
+ # Returns :not_found if the file couldn't be found.
36
+ # Caller should delegate to the default #require.
37
+ # Returns :cover_failed if DeepCover couldn't apply instrumentation the file found.
38
+ # Caller should delegate to the default #require.
39
+ # Returns :not_supported for files that are not supported (such as ike .so files)
40
+ # Caller should delegate to the default #require.
41
+ # Exceptions raised by the required code bubble up as normal.
42
+ # It is *NOT* recommended to simply delegate to the default #require, since it
43
+ # might not be safe to run part of the code again.
44
+ def require(path)
45
+ ext = File.extname(path)
46
+ return :not_supported if ext == '.so'
47
+ path = path + '.rb' if ext != '.rb'
48
+ return false if @loaded_features.include?(path)
49
+
50
+ found_path = resolve_path(path)
51
+
52
+ return :not_found unless found_path
53
+ return false if @loaded_features.include?(found_path)
54
+
55
+ covered_code = cover_and_execute(found_path)
56
+ return covered_code if covered_code.is_a?(Symbol)
57
+
58
+ @loaded_features << found_path
59
+ true
60
+ end
61
+
62
+ # Homemade #load to be able to instrument the code before it gets executed.
63
+ # Note, this doesn't support the `wrap` parameter that ruby's #load has.
64
+ # Same return/raise as CustomRequirer#require, except:
65
+ # Cannot return false since #load doesn't care about a file already being executed.
66
+ def load(path)
67
+ found_path = resolve_path(path)
68
+
69
+ if found_path.nil?
70
+ # #load has a final fallback of always trying relative to current work directory of process
71
+ possible_path = File.absolute_path(path)
72
+ found_path = possible_path if File.exists?(possible_path)
73
+ end
74
+
75
+ return :not_found unless found_path
76
+
77
+ covered_code = cover_and_execute(found_path)
78
+ return covered_code if covered_code.is_a?(Symbol)
79
+
80
+ true
81
+ end
82
+
83
+ protected
84
+ def cover_and_execute(path)
85
+ covered_code = DeepCover.coverage.covered_code(path)
86
+ return :cover_failed unless covered_code
87
+ DeepCover.autoload_tracker.wrap_require(path) do
88
+ covered_code.execute_code
89
+ end
90
+ covered_code
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,8 @@
1
+ module DeepCover
2
+ class Node
3
+ # Reopened in base
4
+ end
5
+ require_relative_dir 'node/mixin'
6
+ require_relative 'node/base'
7
+ require_relative_dir 'node'
8
+ end
@@ -0,0 +1,50 @@
1
+ require_relative 'assignments'
2
+
3
+ module DeepCover
4
+ class Node
5
+ class Arg < Node
6
+ has_child name: Symbol
7
+ def executable?
8
+ false
9
+ end
10
+ end
11
+ Kwarg = Arg
12
+
13
+ class Restarg < Node
14
+ has_child name: [Symbol, nil]
15
+ def executable?
16
+ false
17
+ end
18
+ end
19
+ Kwrestarg = Restarg
20
+
21
+ class Optarg < Node
22
+ has_tracker :default
23
+ has_child name: Symbol
24
+ has_child default: Node, flow_entry_count: :default_tracker_hits, rewrite: '(%{default_tracker};%{node})'
25
+ executed_loc_keys :name, :operator
26
+
27
+ def executable?
28
+ false
29
+ end
30
+ end
31
+ Kwoptarg = Optarg
32
+
33
+ # foo(&block)
34
+ class Blockarg < Node
35
+ has_child name: Symbol
36
+
37
+ def executable?
38
+ false
39
+ end
40
+ end
41
+
42
+ class Args < Node
43
+ has_extra_children arguments: [Arg, Optarg, Restarg, Kwarg, Kwoptarg, Kwrestarg, Blockarg, Mlhs]
44
+
45
+ def executable?
46
+ false
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,250 @@
1
+ require_relative "const"
2
+
3
+ module DeepCover
4
+ class Node
5
+ class VariableAssignment < Node
6
+ has_child var_name: Symbol
7
+ has_child value: [Node, nil]
8
+ executed_loc_keys :name, :operator
9
+
10
+ def execution_count
11
+ return super unless value
12
+ value.flow_completion_count
13
+ end
14
+ end
15
+ Cvasgn = Gvasgn = Ivasgn = Lvasgn = VariableAssignment
16
+
17
+ class Casgn < Node
18
+ has_child cbase: [Cbase, Const, nil]
19
+ has_child var_name: Symbol
20
+ has_child value: [Node, nil]
21
+
22
+ def execution_count
23
+ return super unless value
24
+ value.flow_completion_count
25
+ end
26
+ end
27
+
28
+ class Mlhs < Node
29
+ has_extra_children being_set: Node
30
+ # TODO
31
+ end
32
+
33
+ module BackwardsStrategy
34
+ # Instead of assuming our parent tracks our entry and we are responsible
35
+ # for tracking our completion, we go the other way and assume our parent
36
+ # tracks our completion and we are responsible for our entry.
37
+ def flow_completion_count
38
+ if (s = next_sibling)
39
+ s.flow_entry_count
40
+ else
41
+ parent.flow_completion_count
42
+ end
43
+ end
44
+
45
+ def flow_entry_count
46
+ if (first_child = children_nodes.first)
47
+ first_child.flow_entry_count
48
+ else
49
+ flow_completion_count
50
+ end
51
+ end
52
+ end
53
+
54
+ # a, b = ...
55
+ class Masgn < Node
56
+ class BackwardsNode < Node
57
+ include BackwardsStrategy
58
+ end
59
+
60
+ class SelfReceiver < BackwardsNode
61
+ executed_loc_keys :expression
62
+ end
63
+
64
+ class ConstantCbase < BackwardsNode
65
+ end
66
+
67
+ class DynamicReceiverWrap < Node
68
+ include Wrapper
69
+ has_tracker :entry
70
+ has_child actual_receiver: Node
71
+ def rewrite
72
+ # The local=local is to avoid Ruby warning about "Possible use of value in void context"
73
+ '(%{local} = (%{node});%{entry_tracker}; %{local}=%{local})'
74
+ end
75
+ alias_method :flow_entry_count, :entry_tracker_hits
76
+ end
77
+
78
+ class Setter < Node
79
+ include BackwardsStrategy
80
+ has_child receiver: {self: SelfReceiver, Parser::AST::Node => DynamicReceiverWrap}
81
+ has_child method_name: Symbol
82
+ has_child arg: [Node, nil] # When method is :[]=
83
+ executed_loc_keys :dot, :selector_begin, :selector_end
84
+
85
+ def loc_hash
86
+ base = super
87
+ if method_name == :[]=
88
+ selector = base[:selector]
89
+ {
90
+ expression: base[:expression],
91
+ selector_begin: selector.resize(1),
92
+ # The = is implicit, so only backtrack the end by one
93
+ selector_end: Parser::Source::Range.new(selector.source_buffer, selector.end_pos - 1, selector.end_pos),
94
+ }
95
+ else
96
+ {
97
+ dot: base[:dot],
98
+ expression: base[:expression],
99
+ selector_begin: base[:selector],
100
+ selector_end: nil#,
101
+ }
102
+ end
103
+ end
104
+ end
105
+
106
+ class ConstantScopeWrapper < Node
107
+ include Wrapper
108
+ has_tracker :entry
109
+ has_child actual_node: Node
110
+
111
+ def rewrite
112
+ '(%{entry_tracker};%{node})'
113
+ end
114
+
115
+ def flow_entry_count
116
+ entry_tracker_hits
117
+ end
118
+ end
119
+
120
+ class ConstantAssignment < Node
121
+ include BackwardsStrategy
122
+ has_child scope: [nil], remap: {cbase: ConstantCbase, Parser::AST::Node => ConstantScopeWrapper}
123
+ has_child constant_name: Symbol
124
+
125
+ def execution_count
126
+ scope ? scope.flow_completion_count : super
127
+ end
128
+ end
129
+
130
+ class VariableAssignment < Node
131
+ include BackwardsStrategy
132
+ has_child var_name: Symbol
133
+ end
134
+
135
+ BASE_MAP = {
136
+ cvasgn: VariableAssignment, gvasgn: VariableAssignment,
137
+ ivasgn: VariableAssignment, lvasgn: VariableAssignment,
138
+ casgn: ConstantAssignment,
139
+ send: Setter,
140
+ }
141
+ class Splat < Node
142
+ include BackwardsStrategy
143
+ has_child rest_arg: BASE_MAP
144
+ executed_loc_keys :operator
145
+ end
146
+
147
+ class LeftSide < Node
148
+ include BackwardsStrategy
149
+ has_extra_children receivers: {
150
+ splat: Splat,
151
+ mlhs: LeftSide,
152
+ **BASE_MAP,
153
+ }
154
+ executed_loc_keys # none
155
+
156
+ def flow_completion_count
157
+ parent.flow_completion_count
158
+ end
159
+ end
160
+
161
+ check_completion
162
+
163
+ has_child left: {mlhs: LeftSide}
164
+ has_child value: Node
165
+
166
+ executed_loc_keys :operator
167
+
168
+ def execution_count
169
+ value.flow_completion_count
170
+ end
171
+
172
+ def children_nodes_in_flow_order
173
+ [value, left]
174
+ end
175
+ end
176
+
177
+ class VariableOperatorAssign < Node
178
+ has_child var_name: Symbol
179
+ end
180
+
181
+ class ConstantOperatorAssign < Node
182
+ has_child scope: [Node, nil]
183
+ has_child const_name: Symbol
184
+ def execution_count
185
+ flow_completion_count
186
+ end
187
+ end
188
+
189
+ class SendOperatorAssign < Node
190
+ has_child receiver: [Node, nil]
191
+ has_child method_name: Symbol
192
+ has_extra_children arguments: Node
193
+ executed_loc_keys :dot, :selector_begin, :selector_end, :operator
194
+
195
+ def loc_hash
196
+ base = super
197
+ hash = { expression: base[:expression], begin: base[:begin], end: base[:end], dot: base[:dot]}
198
+ selector = base[:selector]
199
+
200
+ if [:[], :[]=].include?(method_name)
201
+ hash[:selector_begin] = selector.resize(1)
202
+ hash[:selector_end] = Parser::Source::Range.new(selector.source_buffer, selector.end_pos - 1, selector.end_pos)
203
+ else
204
+ hash[:selector_begin] = base[:selector]
205
+ end
206
+
207
+ hash
208
+ end
209
+ end
210
+
211
+ # foo += bar
212
+ class OpAsgn < Node
213
+ check_completion
214
+ has_tracker :reader
215
+ has_child receiver: {
216
+ lvasgn: VariableOperatorAssign, ivasgn: VariableOperatorAssign,
217
+ cvasgn: VariableOperatorAssign, gvasgn: VariableOperatorAssign,
218
+ casgn: Casgn, # TODO
219
+ send: SendOperatorAssign,
220
+ }
221
+ has_child operator: Symbol
222
+ has_child value: Node, rewrite: '(%{reader_tracker};%{node})', flow_entry_count: :reader_tracker_hits
223
+ executed_loc_keys :operator
224
+
225
+ def execution_count
226
+ flow_completion_count
227
+ end
228
+ end
229
+
230
+ # foo ||= bar, foo &&= base
231
+ class BooleanAssignment < Node
232
+ check_completion
233
+ has_tracker :long_branch
234
+ has_child receiver: {
235
+ lvasgn: VariableOperatorAssign, ivasgn: VariableOperatorAssign,
236
+ cvasgn: VariableOperatorAssign, gvasgn: VariableOperatorAssign,
237
+ casgn: ConstantOperatorAssign,
238
+ send: SendOperatorAssign,
239
+ }
240
+ has_child value: Node, rewrite: '(%{long_branch_tracker};%{node})', flow_entry_count: :long_branch_tracker_hits
241
+ executed_loc_keys :operator
242
+
243
+ def execution_count
244
+ flow_completion_count
245
+ end
246
+ end
247
+
248
+ OrAsgn = AndAsgn = BooleanAssignment
249
+ end
250
+ end
@@ -0,0 +1,99 @@
1
+ module DeepCover
2
+ # Base class to handle covered nodes.
3
+ class Node
4
+ include Mixin
5
+ include HasTracker
6
+ include HasChild
7
+ include HasChildHandler
8
+ include CanAugmentChildren
9
+ include Rewriting
10
+ extend CheckCompletion
11
+ include FlowAccounting
12
+ include IsStatement
13
+ include ExecutionLocation
14
+ include ChildCanBeEmpty
15
+
16
+ attr_reader :index, :parent, :children, :base_node
17
+
18
+ def initialize(base_node, parent: raise, index: 0, base_children: base_node.children)
19
+ @base_node = base_node
20
+ @parent = parent
21
+ @index = index
22
+ @children = augment_children(base_children)
23
+ super()
24
+ end
25
+
26
+ ### High level API for coverage purposes
27
+
28
+ def [](v)
29
+ children[v]
30
+ end
31
+
32
+ ### Public API
33
+
34
+ def children_nodes
35
+ children.select{|c| c.is_a? Node }
36
+ end
37
+ alias_method :children_nodes_in_flow_order, :children_nodes
38
+
39
+ def next_sibling
40
+ parent.children_nodes_in_flow_order.each_cons(2) do |child, next_child|
41
+ return next_child if child.equal? self
42
+ end
43
+ nil
44
+ end
45
+
46
+ def previous_sibling
47
+ parent.children_nodes_in_flow_order.each_cons(2) do |previous_child, child|
48
+ return previous_child if child.equal? self
49
+ end
50
+ nil
51
+ end
52
+
53
+ # Adapted from https://github.com/whitequark/ast/blob/master/lib/ast/node.rb
54
+ def to_s(indent=0)
55
+ [
56
+ " " * indent,
57
+ '(',
58
+ fancy_type,
59
+ *children.map do |child, idx|
60
+ if child.is_a?(Node)
61
+ "\n#{child.to_s(indent + 1)}"
62
+ else
63
+ " #{child.inspect}"
64
+ end
65
+ end,
66
+ ')'
67
+ ].join
68
+ end
69
+
70
+ alias_method :inspect, :to_s
71
+ ### Internal API
72
+
73
+ def covered_code
74
+ parent.covered_code
75
+ end
76
+
77
+ def type
78
+ return base_node.type if base_node
79
+ self.class.name.split('::').last.to_sym
80
+ end
81
+
82
+ def each_node(order = :postorder, &block)
83
+ return to_enum :each_node, order unless block_given?
84
+ yield self unless order == :postorder
85
+ children_nodes.each do |child|
86
+ child.each_node(order, &block)
87
+ end
88
+ yield self if order == :postorder
89
+ self
90
+ end
91
+
92
+ def fancy_type
93
+ class_name = self.class.to_s.gsub(/^DeepCover::/,'').gsub(/^Node::/, '')
94
+ t = type.to_s
95
+ t.casecmp(class_name) == 0 ? t : "#{t}[#{class_name}]"
96
+ end
97
+
98
+ end
99
+ end