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,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