deep-cover 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.travis.yml +3 -1
  4. data/Rakefile +1 -1
  5. data/bin/selfcov +1 -1
  6. data/deep_cover.gemspec +2 -1
  7. data/lib/deep_cover/analyser.rb +1 -2
  8. data/lib/deep_cover/analyser/base.rb +20 -6
  9. data/lib/deep_cover/analyser/covered_code_source.rb +0 -12
  10. data/lib/deep_cover/analyser/node.rb +11 -1
  11. data/lib/deep_cover/analyser/optionally_covered.rb +14 -0
  12. data/lib/deep_cover/auto_run.rb +36 -32
  13. data/lib/deep_cover/backports.rb +9 -0
  14. data/lib/deep_cover/base.rb +8 -1
  15. data/lib/deep_cover/cli/debugger.rb +6 -4
  16. data/lib/deep_cover/cli/deep_cover.rb +37 -6
  17. data/lib/deep_cover/cli/instrumented_clone_reporter.rb +50 -32
  18. data/lib/deep_cover/config.rb +16 -7
  19. data/lib/deep_cover/coverage.rb +8 -9
  20. data/lib/deep_cover/covered_code.rb +17 -18
  21. data/lib/deep_cover/node/base.rb +28 -4
  22. data/lib/deep_cover/node/branch.rb +10 -7
  23. data/lib/deep_cover/node/def.rb +2 -2
  24. data/lib/deep_cover/node/empty_body.rb +1 -1
  25. data/lib/deep_cover/node/mixin/can_augment_children.rb +1 -2
  26. data/lib/deep_cover/node/mixin/execution_location.rb +9 -6
  27. data/lib/deep_cover/node/mixin/has_child.rb +16 -17
  28. data/lib/deep_cover/node/mixin/has_tracker.rb +2 -6
  29. data/lib/deep_cover/node/mixin/rewriting.rb +9 -8
  30. data/lib/deep_cover/node/send.rb +57 -48
  31. data/lib/deep_cover/node/{boolean.rb → short_circuit.rb} +0 -0
  32. data/lib/deep_cover/reporter/istanbul.rb +2 -3
  33. data/lib/deep_cover/tools.rb +2 -1
  34. data/lib/deep_cover/tools/dasherize.rb +8 -0
  35. data/lib/deep_cover/tools/dump_covered_code.rb +12 -6
  36. data/lib/deep_cover/tools/format_char_cover.rb +2 -3
  37. data/lib/deep_cover/tools/slice.rb +7 -0
  38. data/lib/deep_cover/version.rb +1 -1
  39. metadata +7 -18
@@ -6,8 +6,8 @@ module DeepCover
6
6
  allow_partial: false,
7
7
  }
8
8
 
9
- def initialize
10
- @options = copy(DEFAULTS)
9
+ def initialize(**options)
10
+ @options = copy(DEFAULTS.merge(options))
11
11
  end
12
12
 
13
13
  def to_hash
@@ -16,12 +16,14 @@ module DeepCover
16
16
  alias_method :to_h, :to_hash
17
17
 
18
18
  def ignore_uncovered(*keywords)
19
- @options[:ignore_uncovered] -= keywords
19
+ check_uncovered(keywords)
20
+ @options[:ignore_uncovered] += keywords
20
21
  self
21
22
  end
22
23
 
23
24
  def detect_uncovered(*keywords)
24
- @options[:ignore_uncovered] += keywords
25
+ check_uncovered(keywords)
26
+ @options[:ignore_uncovered] -= keywords
25
27
  self
26
28
  end
27
29
 
@@ -31,20 +33,27 @@ module DeepCover
31
33
  end
32
34
 
33
35
  private
36
+ def check_uncovered(keywords)
37
+ unknown = keywords - Analyser.optionally_covered
38
+ raise ArgumentError, "unknown options: #{unknown.join(', ')}" unless unknown.empty?
39
+ end
40
+
34
41
  def copy(h)
35
42
  h.dup.transform_values(&:dup)
36
43
  end
37
44
 
38
45
  module Setter
39
- def configure(&block)
46
+ def config
40
47
  @config ||= Config.new
48
+ end
41
49
 
50
+ def configure(&block)
42
51
  raise "Must provide a block" unless block
43
52
  case block.arity
44
53
  when 0
45
- @config.instance_eval(&block)
54
+ config.instance_eval(&block)
46
55
  when 1
47
- block.call(@config)
56
+ block.call(config)
48
57
  end
49
58
  end
50
59
  end
@@ -4,7 +4,6 @@ module DeepCover
4
4
  require 'parser/current'
5
5
  end
6
6
  require 'pry'
7
- require 'pathname'
8
7
  require_relative 'covered_code'
9
8
  require 'securerandom'
10
9
 
@@ -29,9 +28,9 @@ module DeepCover
29
28
  covered_code(filename).line_coverage(**options)
30
29
  end
31
30
 
32
- def covered_code(path)
31
+ def covered_code(path, **options)
33
32
  raise 'path must be an absolute path' unless Pathname.new(path).absolute?
34
- @covered_codes[path] ||= CoveredCode.new(path: path, **@options)
33
+ @covered_codes[path] ||= CoveredCode.new(path: path, **options, **@options)
35
34
  end
36
35
 
37
36
  def each
@@ -57,7 +56,7 @@ module DeepCover
57
56
 
58
57
  def report_istanbul(output: nil, **options)
59
58
  dir = output_istanbul(**options).dirname
60
- if output
59
+ unless [nil, '', 'false'].include? output
61
60
  output = File.expand_path(output)
62
61
  html = "--reporter=html --report-dir='#{output}' && open '#{output}/index.html'"
63
62
  end
@@ -89,8 +88,8 @@ module DeepCover
89
88
  end
90
89
  end
91
90
 
92
- def self.load(dest_path, dirname = 'deep_cover')
93
- Persistence.new(dest_path, dirname).load
91
+ def self.load(dest_path, dirname = 'deep_cover', with_trackers: true)
92
+ Persistence.new(dest_path, dirname).load(with_trackers: with_trackers)
94
93
  end
95
94
 
96
95
  def self.saved?(dest_path, dirname = 'deep_cover')
@@ -108,7 +107,7 @@ module DeepCover
108
107
  end
109
108
 
110
109
  def tracker_global
111
- @options[:tracker_global]
110
+ @options.fetch(:tracker_global, CoveredCode::DEFAULT_TRACKER_GLOBAL)
112
111
  end
113
112
 
114
113
  class Persistence
@@ -120,9 +119,9 @@ module DeepCover
120
119
  @dir_path = Pathname(dest_path).join(dirname).expand_path
121
120
  end
122
121
 
123
- def load
122
+ def load(with_trackers: true)
124
123
  saved?
125
- load_trackers
124
+ load_trackers if with_trackers
126
125
  load_coverage
127
126
  end
128
127
 
@@ -1,10 +1,12 @@
1
1
  module DeepCover
2
2
  class CoveredCode
3
- attr_accessor :covered_source, :buffer, :tracker_global, :local_var
3
+ DEFAULT_TRACKER_GLOBAL = '$_cov'
4
+
5
+ attr_accessor :covered_source, :buffer, :tracker_global, :local_var, :name
4
6
  @@counter = 0
5
7
  @@globals = Hash.new{|h, global| h[global] = eval("#{global} ||= {}") }
6
8
 
7
- def initialize(path: nil, source: nil, lineno: nil, tracker_global: '$_cov', local_var: '_temp')
9
+ def initialize(path: nil, source: nil, lineno: nil, tracker_global: DEFAULT_TRACKER_GLOBAL, local_var: '_temp', name: nil)
8
10
  raise "Must provide either path or source" unless path || source
9
11
 
10
12
  @buffer = ::Parser::Source::Buffer.new(path)
@@ -17,6 +19,7 @@ module DeepCover
17
19
  @tracker_count = 0
18
20
  @tracker_global = tracker_global
19
21
  @local_var = local_var
22
+ @name = name || (source ? '(source)' : File.basename(path))
20
23
  @covered_source = instrument_source
21
24
  end
22
25
 
@@ -24,10 +27,6 @@ module DeepCover
24
27
  @buffer.name || "(source: '#{@buffer.source[0..20]}...')"
25
28
  end
26
29
 
27
- def name
28
- @buffer.name ? File.basename(@buffer.name) : "(source)"
29
- end
30
-
31
30
  def nb_lines
32
31
  @nb_lines ||= begin
33
32
  lines = buffer.source_lines
@@ -95,7 +94,7 @@ module DeepCover
95
94
 
96
95
  def root
97
96
  @root ||= begin
98
- ast = Parser::CurrentRuby.new.parse(@buffer)
97
+ ast = DeepCover.parser.parse(@buffer)
99
98
  Node::Root.new(ast, self)
100
99
  end
101
100
  end
@@ -106,17 +105,17 @@ module DeepCover
106
105
 
107
106
  def instrument_source
108
107
  rewriter = ::Parser::Source::Rewriter.new(@buffer)
109
- covered_ast.each_node do |node|
110
- prefix, suffix = node.rewrite_prefix_suffix
111
- unless prefix.empty?
112
- expression = node.expression
113
- prefix = yield prefix, node, expression.begin, :prefix if block_given?
114
- rewriter.insert_before_multi expression, prefix rescue binding.pry
115
- end
116
- unless suffix.empty?
117
- expression = node.expression
118
- suffix = yield suffix, node, expression.end, :suffix if block_given?
119
- rewriter.insert_after_multi expression, suffix
108
+ covered_ast.each_node(:postorder) do |node|
109
+ node.rewriting_rules.each do |range, rule|
110
+ prefix, _node, suffix = rule.partition('%{node}')
111
+ unless prefix.empty?
112
+ prefix = yield prefix, node, range.begin, :prefix if block_given?
113
+ rewriter.insert_before_multi range, prefix rescue binding.pry
114
+ end
115
+ unless suffix.empty?
116
+ suffix = yield suffix, node, range.end, :suffix if block_given?
117
+ rewriter.insert_after_multi range, suffix
118
+ end
120
119
  end
121
120
  end
122
121
  rewriter.process
@@ -19,17 +19,25 @@ module DeepCover
19
19
  @base_node = base_node
20
20
  @parent = parent
21
21
  @index = index
22
- @children = augment_children(base_children)
22
+ @children = begin
23
+ augment_children(base_children)
24
+ rescue StandardError => e
25
+ diagnose(e)
26
+ end
23
27
  super()
24
28
  end
25
29
 
26
- ### High level API for coverage purposes
30
+ ### Public API
27
31
 
32
+ # Shortcut to access children
28
33
  def [](v)
29
34
  children[v]
30
35
  end
31
36
 
32
- ### Public API
37
+ # Shortcut to create a node from source code
38
+ def self.[](source)
39
+ CoveredCode.new(source: source).execute_code.covered_ast
40
+ end
33
41
 
34
42
  def children_nodes
35
43
  children.select{|c| c.is_a? Node }
@@ -75,7 +83,7 @@ module DeepCover
75
83
  end
76
84
 
77
85
  def type
78
- return base_node.type if base_node
86
+ return base_node.type if base_node.respond_to? :type
79
87
  self.class.name.split('::').last.to_sym
80
88
  end
81
89
 
@@ -95,5 +103,21 @@ module DeepCover
95
103
  t.casecmp(class_name) == 0 ? t : "#{t}[#{class_name}]"
96
104
  end
97
105
 
106
+ private
107
+ def diagnose(exception)
108
+ if self.class == Node
109
+ exp = base_node.loc.expression
110
+ warn ["Unknown node type encountered: #{base_node.type}",
111
+ 'This node will not be handled properly; its subnodes will be ignored',
112
+ 'Source:',
113
+ exp && exp.source,
114
+ "Original exception:",
115
+ exception.inspect,
116
+ ].join("\n")
117
+ []
118
+ else
119
+ raise exception
120
+ end
121
+ end
98
122
  end
99
123
  end
@@ -1,3 +1,5 @@
1
+ require_relative 'empty_body'
2
+
1
3
  module DeepCover
2
4
  class Node
3
5
  module Branch
@@ -13,14 +15,15 @@ module DeepCover
13
15
  # Also define flow_entry_count
14
16
  end
15
17
 
16
- class TrivialBranch < Struct.new(:condition, :other_branch)
17
- def flow_entry_count
18
- condition.flow_completion_count - other_branch.flow_entry_count
18
+ class TrivialBranch < Node::EmptyBody
19
+ def initialize(condition, other_branch, position: true)
20
+ @condition = condition
21
+ @other_branch = other_branch
22
+ super(nil, parent: condition.parent, position: position)
19
23
  end
20
- alias_method :flow_completion_count, :flow_entry_count
21
- alias_method :execution_count, :flow_entry_count
22
- def executable?
23
- true
24
+
25
+ def flow_entry_count
26
+ @condition.flow_completion_count - @other_branch.flow_entry_count
24
27
  end
25
28
  end
26
29
 
@@ -7,7 +7,7 @@ module DeepCover
7
7
  has_child method_name: Symbol
8
8
  has_child signature: Args
9
9
  has_child body: Node,
10
- rewrite: '%{method_call_tracker};%{local}=nil;%{node};',
10
+ rewrite: '%{method_call_tracker};%{local}=nil;%{node}',
11
11
  can_be_empty: -> { base_node.loc.end.begin },
12
12
  is_statement: true,
13
13
  flow_entry_count: :method_call_tracker_hits
@@ -25,7 +25,7 @@ module DeepCover
25
25
  has_child method_name: Symbol
26
26
  has_child signature: Args
27
27
  has_child body: Node,
28
- rewrite: '%{method_call_tracker};%{local}=nil;%{node};',
28
+ rewrite: '%{method_call_tracker};%{local}=nil;%{node}',
29
29
  can_be_empty: -> { base_node.loc.end.begin },
30
30
  is_statement: true,
31
31
  flow_entry_count: :method_call_tracker_hits
@@ -2,7 +2,6 @@ module DeepCover
2
2
  class Node::EmptyBody < Node
3
3
  def initialize(base_node, parent: raise, index: 0, position: ChildCanBeEmpty.last_empty_position)
4
4
  @position = position
5
- @position = parent.expression.begin if @position == true # Some random position... don't rewrite!
6
5
  super(base_node, parent: parent, index: index, base_children: [])
7
6
  end
8
7
 
@@ -11,6 +10,7 @@ module DeepCover
11
10
  end
12
11
 
13
12
  def loc_hash
13
+ return {} if @position == true
14
14
  {expression: @position}
15
15
  end
16
16
 
@@ -15,8 +15,7 @@ module DeepCover
15
15
  child_base_nodes = [*child_base_nodes, *Array.new(missing)]
16
16
  end
17
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
-
18
+ child_name = self.class.child_index_to_name(child_index, child_base_nodes.size)
20
19
  if (klass = remap_child(child, child_name))
21
20
  klass.new(child, parent: self, index: child_index)
22
21
  else
@@ -25,17 +25,20 @@ module DeepCover
25
25
  nil
26
26
  end
27
27
 
28
- def executed_locs
28
+ def executed_loc_hash
29
+ h = Tools.slice(loc_hash, *executed_loc_keys)
29
30
  if (keys = parent.child_executed_loc_keys(self))
30
- inherited = parent.loc_hash.values_at(*keys)
31
+ h.merge!(Tools.slice(parent.loc_hash, *keys))
31
32
  end
32
- [ *loc_hash.values_at(*executed_loc_keys),
33
- *inherited
34
- ].compact
33
+ h.reject{|k, v| v.nil? }
34
+ end
35
+
36
+ def executed_locs
37
+ executed_loc_hash.values
35
38
  end
36
39
 
37
40
  def loc_hash
38
- @loc_hash ||= base_node.location.to_hash
41
+ @loc_hash ||= (base_node.respond_to?(:location) ? base_node.location.to_hash : {}).freeze
39
42
  end
40
43
 
41
44
  def expression
@@ -3,14 +3,9 @@ module DeepCover
3
3
  module HasChild
4
4
  def self.included(base)
5
5
  base.extend ClassMethods
6
- setup_constants(base)
7
6
  end
8
-
9
- def self.setup_constants(subclass)
10
- subclass.const_set :CHILDREN, {}
11
- subclass.const_set :CHILDREN_TYPES, {}
12
- end
13
-
7
+ CHILDREN = {}
8
+ CHILDREN_TYPES = {}
14
9
 
15
10
  def initialize(*)
16
11
  super
@@ -25,18 +20,25 @@ module DeepCover
25
20
  end
26
21
 
27
22
  module ClassMethods
28
- def has_child(rest: false, **h)
23
+ def has_child(_rest: false, _refine: false, **h)
29
24
  raise "Needs exactly one custom named argument, got #{h.size}" if h.size != 1
30
25
  name, types = h.first
31
26
  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)
27
+ update_children_const(name, rest: _rest) unless _refine
28
+ define_accessor(name) unless _refine
34
29
  add_runtime_check(name, types)
35
30
  self
36
31
  end
37
32
 
38
33
  def has_extra_children(**h)
39
- has_child(**h, rest: true)
34
+ has_child(**h, _rest: true)
35
+ end
36
+
37
+ def refine_child(child_name = nil, **h)
38
+ if child_name
39
+ h = {child_name => self::CHILDREN_TYPES.fetch(child_name), **h}
40
+ end
41
+ has_child(**h, _refine: true)
40
42
  end
41
43
 
42
44
  def child_index_to_name(index, nb_children)
@@ -91,14 +93,11 @@ module DeepCover
91
93
  end
92
94
 
93
95
  def inherited(subclass)
94
- HasChild.setup_constants(subclass)
96
+ subclass.const_set :CHILDREN, self::CHILDREN.dup
97
+ subclass.const_set :CHILDREN_TYPES, self::CHILDREN_TYPES.dup
95
98
  super
96
99
  end
97
100
 
98
- def const_missing(name)
99
- const_set(name, self::CHILDREN.fetch(name.downcase) { return super })
100
- end
101
-
102
101
  def update_children_const(name, rest: false)
103
102
  children_map = self::CHILDREN
104
103
  already_has_rest = false
@@ -124,7 +123,7 @@ module DeepCover
124
123
  warn "child name '#{name}' conflicts with existing method for #{self}" if method_defined? name
125
124
  class_eval <<-end_eval, __FILE__, __LINE__
126
125
  def #{name}
127
- children[#{name.upcase}]
126
+ children[CHILDREN.fetch(#{name.inspect})]
128
127
  end
129
128
  end_eval
130
129
  end
@@ -3,12 +3,8 @@ module DeepCover
3
3
  module HasTracker
4
4
  def self.included(base)
5
5
  base.extend ClassMethods
6
- setup_constants(base)
7
- end
8
-
9
- def self.setup_constants(base)
10
- base.const_set :TRACKERS, {}
11
6
  end
7
+ TRACKERS = {}
12
8
 
13
9
  def initialize(*)
14
10
  @tracker_offset = covered_code.allocate_trackers(self.class::TRACKERS.size).begin
@@ -23,8 +19,8 @@ module DeepCover
23
19
 
24
20
  module ClassMethods
25
21
  def inherited(base)
22
+ base.const_set :TRACKERS, self::TRACKERS.dup
26
23
  super
27
- HasTracker.setup_constants(base)
28
24
  end
29
25
 
30
26
  def has_tracker(name)