deep-cover 0.1.14 → 0.1.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +227 -0
  3. data/Gemfile +5 -2
  4. data/Rakefile +9 -6
  5. data/bin/console +3 -3
  6. data/bin/cov +8 -8
  7. data/bin/gemcov +2 -2
  8. data/bin/selfcov +5 -5
  9. data/bin/test_gems +11 -10
  10. data/bin/testall +6 -6
  11. data/deep_cover.gemspec +26 -21
  12. data/exe/deep-cover +1 -0
  13. data/lib/deep-cover.rb +2 -0
  14. data/lib/deep_cover.rb +3 -0
  15. data/lib/deep_cover/analyser.rb +2 -0
  16. data/lib/deep_cover/analyser/base.rb +2 -0
  17. data/lib/deep_cover/analyser/branch.rb +4 -2
  18. data/lib/deep_cover/analyser/covered_code_source.rb +3 -1
  19. data/lib/deep_cover/analyser/function.rb +3 -1
  20. data/lib/deep_cover/analyser/ignore_uncovered.rb +6 -4
  21. data/lib/deep_cover/analyser/node.rb +3 -0
  22. data/lib/deep_cover/analyser/optionally_covered.rb +12 -7
  23. data/lib/deep_cover/analyser/per_char.rb +7 -6
  24. data/lib/deep_cover/analyser/per_line.rb +9 -8
  25. data/lib/deep_cover/analyser/statement.rb +2 -0
  26. data/lib/deep_cover/analyser/subset.rb +4 -1
  27. data/lib/deep_cover/auto_run.rb +3 -0
  28. data/lib/deep_cover/autoload_tracker.rb +6 -3
  29. data/lib/deep_cover/backports.rb +2 -0
  30. data/lib/deep_cover/base.rb +11 -2
  31. data/lib/deep_cover/builtin_takeover.rb +2 -0
  32. data/lib/deep_cover/cli/debugger.rb +55 -30
  33. data/lib/deep_cover/cli/deep_cover.rb +17 -11
  34. data/lib/deep_cover/cli/instrumented_clone_reporter.rb +16 -14
  35. data/lib/deep_cover/config.rb +29 -16
  36. data/lib/deep_cover/core_ext/autoload_overrides.rb +2 -0
  37. data/lib/deep_cover/core_ext/coverage_replacement.rb +2 -0
  38. data/lib/deep_cover/core_ext/load_overrides.rb +5 -6
  39. data/lib/deep_cover/core_ext/require_overrides.rb +6 -7
  40. data/lib/deep_cover/coverage.rb +21 -18
  41. data/lib/deep_cover/covered_code.rb +22 -12
  42. data/lib/deep_cover/custom_requirer.rb +82 -35
  43. data/lib/deep_cover/memoize.rb +48 -0
  44. data/lib/deep_cover/module_override.rb +2 -0
  45. data/lib/deep_cover/node.rb +14 -1
  46. data/lib/deep_cover/node/arguments.rb +2 -0
  47. data/lib/deep_cover/node/assignments.rb +32 -30
  48. data/lib/deep_cover/node/base.rb +30 -29
  49. data/lib/deep_cover/node/begin.rb +3 -1
  50. data/lib/deep_cover/node/block.rb +5 -2
  51. data/lib/deep_cover/node/branch.rb +2 -1
  52. data/lib/deep_cover/node/case.rb +15 -13
  53. data/lib/deep_cover/node/collections.rb +2 -0
  54. data/lib/deep_cover/node/const.rb +2 -0
  55. data/lib/deep_cover/node/def.rb +10 -8
  56. data/lib/deep_cover/node/empty_body.rb +2 -0
  57. data/lib/deep_cover/node/exceptions.rb +3 -1
  58. data/lib/deep_cover/node/if.rb +3 -1
  59. data/lib/deep_cover/node/keywords.rb +4 -2
  60. data/lib/deep_cover/node/literals.rb +2 -0
  61. data/lib/deep_cover/node/loops.rb +5 -3
  62. data/lib/deep_cover/node/mixin/can_augment_children.rb +8 -7
  63. data/lib/deep_cover/node/mixin/check_completion.rb +3 -1
  64. data/lib/deep_cover/node/mixin/child_can_be_empty.rb +4 -2
  65. data/lib/deep_cover/node/mixin/executed_after_children.rb +2 -0
  66. data/lib/deep_cover/node/mixin/execution_location.rb +4 -2
  67. data/lib/deep_cover/node/mixin/flow_accounting.rb +2 -0
  68. data/lib/deep_cover/node/mixin/has_child.rb +22 -18
  69. data/lib/deep_cover/node/mixin/has_child_handler.rb +10 -8
  70. data/lib/deep_cover/node/mixin/has_tracker.rb +4 -2
  71. data/lib/deep_cover/node/mixin/is_statement.rb +3 -1
  72. data/lib/deep_cover/node/mixin/rewriting.rb +5 -3
  73. data/lib/deep_cover/node/mixin/wrapper.rb +2 -0
  74. data/lib/deep_cover/node/module.rb +11 -9
  75. data/lib/deep_cover/node/root.rb +2 -0
  76. data/lib/deep_cover/node/send.rb +4 -2
  77. data/lib/deep_cover/node/short_circuit.rb +4 -2
  78. data/lib/deep_cover/node/splat.rb +2 -0
  79. data/lib/deep_cover/node/variables.rb +2 -0
  80. data/lib/deep_cover/parser_ext/range.rb +3 -1
  81. data/lib/deep_cover/problem_with_diagnostic.rb +11 -9
  82. data/lib/deep_cover/reporter.rb +2 -0
  83. data/lib/deep_cover/reporter/istanbul.rb +28 -24
  84. data/lib/deep_cover/tools.rb +2 -0
  85. data/lib/deep_cover/tools/builtin_coverage.rb +6 -4
  86. data/lib/deep_cover/tools/camelize.rb +3 -1
  87. data/lib/deep_cover/tools/dasherize.rb +3 -1
  88. data/lib/deep_cover/tools/dump_covered_code.rb +7 -6
  89. data/lib/deep_cover/tools/execute_sample.rb +13 -13
  90. data/lib/deep_cover/tools/format.rb +3 -1
  91. data/lib/deep_cover/tools/format_char_cover.rb +4 -2
  92. data/lib/deep_cover/tools/format_generated_code.rb +3 -1
  93. data/lib/deep_cover/tools/number_lines.rb +2 -0
  94. data/lib/deep_cover/tools/our_coverage.rb +5 -3
  95. data/lib/deep_cover/tools/profiling.rb +66 -0
  96. data/lib/deep_cover/tools/require_relative_dir.rb +3 -1
  97. data/lib/deep_cover/tools/silence_warnings.rb +4 -1
  98. data/lib/deep_cover/tools/slice.rb +3 -1
  99. data/lib/deep_cover/tools/truncate_backtrace.rb +2 -0
  100. data/lib/deep_cover/version.rb +3 -1
  101. metadata +47 -30
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
4
5
  require 'deep_cover/cli/deep_cover'
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'deep_cover'
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Style/MixinUsage (See https://github.com/bbatsov/rubocop/issues/5055)
1
4
  module DeepCover
2
5
  require 'parser'
3
6
  require 'term/ansicolor'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'node'
2
4
  require_relative 'covered_code'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  module Analyser::Base
3
5
  attr_reader :source, :options
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'subset'
2
4
 
3
5
  module DeepCover
4
6
  class Analyser::Branch < Analyser
5
7
  include Analyser::Subset
6
- SUBSET_CLASSES = [Node::Branch]
8
+ SUBSET_CLASSES = [Node::Branch].freeze
7
9
 
8
10
  def results
9
11
  each_node.map do |node, _children|
10
- branches_runs = node.branches.map{|b| [b, node_runs(b)]}.to_h
12
+ branches_runs = node.branches.map { |b| [b, node_runs(b)] }.to_h
11
13
  [node, branches_runs]
12
14
  end.to_h
13
15
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  class Analyser::CoveredCodeSource < Analyser
3
5
  attr_reader :covered_code
4
6
 
5
7
  def initialize(covered_code)
6
- @covered_code = covered_code
8
+ @covered_code = covered_code.freeze
7
9
  end
8
10
 
9
11
  # Looking exclusively at our subset of nodes, returns the node's direct descendants
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'subset'
2
4
 
3
5
  module DeepCover
4
6
  class Analyser::Function < Analyser
5
7
  include Analyser::Subset
6
- SUBSET_CLASSES = [Node::Block, Node::Defs, Node::Def]
8
+ SUBSET_CLASSES = [Node::Block, Node::Defs, Node::Def].freeze
7
9
 
8
10
  def node_runs(node)
9
11
  super(node.body)
@@ -1,16 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  module Analyser::IgnoreUncovered
3
5
  def initialize(source, ignore_uncovered: [], **options)
4
6
  super
5
7
  @allow_filters = Array(ignore_uncovered)
6
- .map{|kind| :"is_#{kind}?"}
7
- .select{|name| respond_to?(name) }
8
- .map{|name| method(name)} # So was tempted to write `.map(&method(:method))`!
8
+ .map { |kind| :"is_#{kind}?" }
9
+ .select { |name| respond_to?(name) }
10
+ .map { |name| method(name) } # So was tempted to write `.map(&method(:method))`!
9
11
  end
10
12
 
11
13
  def node_runs(node)
12
14
  runs = super
13
- if runs == 0 && @allow_filters.any?{ |f| f[node] }
15
+ if runs == 0 && @allow_filters.any? { |f| f[node] }
14
16
  runs = nil
15
17
  end
16
18
  runs
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  class Analyser::Node < Analyser
3
5
  def is_raise?(node)
@@ -14,6 +16,7 @@ module DeepCover
14
16
  end
15
17
 
16
18
  protected
19
+
17
20
  def convert(node, **)
18
21
  Analyser::CoveredCodeSource.new(node)
19
22
  end
@@ -1,14 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  module Analyser::OptionallyCovered
3
5
  def optionally_covered
4
6
  @optionally_covered ||= Analyser
5
- .constants.map{|c| Analyser.const_get(c)}
6
- .select{|klass| klass < Analyser }
7
- .flat_map do |klass|
8
- klass.instance_methods(false).map {|m| m.match(/^is_(.*)\?$/); $1 }
9
- end
10
- .compact
11
- .map(&:to_sym)
7
+ .constants.map { |c| Analyser.const_get(c) }
8
+ .select { |klass| klass < Analyser }
9
+ .flat_map do |klass|
10
+ klass.instance_methods(false).map do |method|
11
+ method =~ /^is_(.*)\?$/
12
+ Regexp.last_match(1)
13
+ end
14
+ end
15
+ .compact
16
+ .map(&:to_sym)
12
17
  end
13
18
  end
14
19
  end
@@ -1,19 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  class Analyser::PerChar < Analyser
3
5
  # Returns an array of characters for each line of code.
4
6
  # Each character is either ' ' (executed), '-' (not executable) or 'x' (not covered)
5
7
  def results
6
8
  buffer = covered_code.buffer
7
- bc = buffer.source_lines.map{|line| '-' * line.size}
9
+ bc = buffer.source_lines.map { |line| '-' * line.size }
8
10
  each_node do |node, _children|
9
11
  runs = node_runs(node)
10
- if runs != nil
11
- node.proper_range.each do |pos|
12
- bc[buffer.line_for_position(pos)-buffer.first_line][buffer.column_for_position(pos)] = runs > 0 ? ' ' : 'x'
13
- end
12
+ next if runs == nil
13
+ node.proper_range.each do |pos|
14
+ bc[buffer.line_for_position(pos) - buffer.first_line][buffer.column_for_position(pos)] = runs > 0 ? ' ' : 'x'
14
15
  end
15
16
  end
16
- bc.zip(buffer.source_lines){|cov, line| cov[line.size..-1] = ''} # remove extraneous character for end lines, in any
17
+ bc.zip(buffer.source_lines) { |cov, line| cov[line.size..-1] = '' } # remove extraneous character for end lines, in any
17
18
  bc
18
19
  end
19
20
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  class Analyser::PerLine < Analyser
3
5
  # Returns an array of runs, one per line.
@@ -5,15 +7,14 @@ module DeepCover
5
7
  disallow_partial = !options.fetch(:allow_partial, true)
6
8
  line_hits = Array.new(covered_code.nb_lines + covered_code.lineno - 1)
7
9
  each_node do |node, _children|
8
- if (runs = node_runs(node))
9
- node.executed_locs.each do |loc|
10
- lineno = loc.line - 1
11
- if disallow_partial
12
- line_hits[lineno] = 0 if runs == 0
13
- next if line_hits[lineno] == 0
14
- end
15
- line_hits[lineno] = [line_hits[lineno] || 0, runs].max
10
+ next unless (runs = node_runs(node))
11
+ node.executed_locs.each do |loc|
12
+ lineno = loc.line - 1
13
+ if disallow_partial
14
+ line_hits[lineno] = 0 if runs == 0
15
+ next if line_hits[lineno] == 0
16
16
  end
17
+ line_hits[lineno] = [line_hits[lineno] || 0, runs].max
17
18
  end
18
19
  end
19
20
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'subset'
2
4
 
3
5
  module DeepCover
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  # A module to create a subset from a criteria called `in_subset?`
3
5
  # Including classes can refine it, or specify SUBSET_CLASSES
@@ -7,6 +9,7 @@ module DeepCover
7
9
  end
8
10
 
9
11
  private
12
+
10
13
  def find_children(from, parent = from)
11
14
  @source.node_children(from).flat_map do |node|
12
15
  if in_subset?(node, parent)
@@ -18,7 +21,7 @@ module DeepCover
18
21
  end
19
22
 
20
23
  def in_subset?(node, _parent)
21
- self.class::SUBSET_CLASSES.any?{|klass| node.is_a?(klass)}
24
+ self.class::SUBSET_CLASSES.any? { |klass| node.is_a?(klass) }
22
25
  end
23
26
  end
24
27
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'deep_cover'
2
4
  require 'pry'
3
5
 
@@ -15,6 +17,7 @@ module DeepCover
15
17
  end
16
18
 
17
19
  private
20
+
18
21
  def detect
19
22
  Coverage.saved? @covered_path
20
23
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'weakref'
2
4
 
3
5
  module DeepCover
@@ -10,7 +12,7 @@ module DeepCover
10
12
  ext = File.extname(path)
11
13
  # We don't care about .so files
12
14
  return if ext == '.so'
13
- path = path + '.rb' if ext != '.rb'
15
+ path += '.rb' if ext != '.rb'
14
16
 
15
17
  pairs = @autoloaded_paths[path] ||= []
16
18
  pairs << [WeakRef.new(const), name]
@@ -21,7 +23,7 @@ module DeepCover
21
23
 
22
24
  paths.flat_map do |path|
23
25
  pairs = @autoloaded_paths[path] || []
24
- pairs = pairs.map{|weak_const, name| [self.class.value_from_weak_ref(weak_const), name] }
26
+ pairs = pairs.map { |weak_const, name| [self.class.value_from_weak_ref(weak_const), name] }
25
27
  pairs.select!(&:first)
26
28
  pairs
27
29
  end
@@ -49,9 +51,10 @@ module DeepCover
49
51
 
50
52
  def initialize_autoloaded_paths
51
53
  @autoloaded_paths = {}
54
+ # This is only used on MRI, so ObjectSpace is alright.
52
55
  ObjectSpace.each_object(Module) do |mod|
53
56
  mod.constants.each do |name|
54
- if path = mod.autoload?(name)
57
+ if (path = mod.autoload?(name))
55
58
  add(mod, name, path)
56
59
  end
57
60
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # We use a few features newer than our target of Ruby 2.0+:
2
4
  class Module
3
5
  public :prepend # Public in Ruby 2.1+.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  module Base
3
5
  def start
@@ -35,12 +37,19 @@ module DeepCover
35
37
  stop
36
38
  end
37
39
 
40
+ def config_changed(what)
41
+ if what == :paths
42
+ warn "Changing DeepCover's paths after starting coverage is highly discouraged" if @started
43
+ @custom_requirer = nil
44
+ end
45
+ end
46
+
38
47
  def coverage
39
48
  @coverage ||= Coverage.new
40
49
  end
41
50
 
42
51
  def custom_requirer
43
- @custom_requirer ||= CustomRequirer.new
52
+ @custom_requirer ||= CustomRequirer.new(lookup_paths: config.paths)
44
53
  end
45
54
 
46
55
  def autoload_tracker
@@ -49,7 +58,7 @@ module DeepCover
49
58
 
50
59
  def handle_relative_filename(filename)
51
60
  unless Pathname.new(filename).absolute?
52
- relative_to = File.dirname(caller[1].partition(/\.rb:\d/).first)
61
+ relative_to = File.dirname(caller(2..2).first.partition(/\.rb:\d/).first)
53
62
  filename = File.absolute_path(filename, relative_to)
54
63
  end
55
64
  filename += '.rb' unless filename =~ /\.rb$/
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../deep_cover'
2
4
  require_relative '../deep_cover/core_ext/coverage_replacement'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  module CLI
3
5
  class Debugger
@@ -6,15 +8,15 @@ module DeepCover
6
8
  module ColorAST
7
9
  def fancy_type
8
10
  color = case
9
- when !executable?
10
- :faint
11
- when !was_executed?
12
- :red
13
- when flow_interrupt_count > 0
14
- :yellow
15
- else
16
- :green
17
- end
11
+ when !executable?
12
+ :faint
13
+ when !was_executed?
14
+ :red
15
+ when flow_interrupt_count > 0
16
+ :yellow
17
+ else
18
+ :green
19
+ end
18
20
  Term::ANSIColor.send(color, super)
19
21
  end
20
22
  end
@@ -29,46 +31,51 @@ module DeepCover
29
31
  end
30
32
 
31
33
  def show
32
- execute
33
- if @debug
34
- show_line_coverage
35
- show_instrumented_code
36
- show_ast
34
+ Tools.profile(options[:profile]) do
35
+ execute
36
+ covered_code.freeze # Our output relies on the counts, so better freeze. See [#13]
37
+ if @debug
38
+ show_line_coverage
39
+ show_instrumented_code
40
+ show_ast
41
+ end
42
+ show_char_coverage
37
43
  end
38
- show_char_coverage
39
44
  pry if @debug
40
45
  finish
41
46
  end
42
47
 
43
48
  def show_line_coverage
44
- puts "Line Coverage: Builtin | DeepCover | DeepCover Strict:\n"
49
+ output { "Line Coverage: Builtin | DeepCover | DeepCover Strict:\n" }
45
50
  begin
46
51
  builtin_line_coverage = builtin_coverage(@source, @filename, @lineno)
47
52
  our_line_coverage = our_coverage(@source, @filename, @lineno, **options)
48
53
  our_strict_line_coverage = our_coverage(@source, @filename, @lineno, allow_partial: false, **options)
49
- lines = format(builtin_line_coverage, our_line_coverage, our_strict_line_coverage, source: @source)
50
- puts number_lines(lines, lineno: @lineno)
54
+ output do
55
+ lines = format(builtin_line_coverage, our_line_coverage, our_strict_line_coverage, source: @source)
56
+ number_lines(lines, lineno: @lineno)
57
+ end
51
58
  rescue Exception => e
52
- puts "Can't run coverage: #{e.class.name}: #{e}\n#{e.backtrace.join("\n")}"
59
+ output { "Can't run coverage: #{e.class.name}: #{e}\n#{e.backtrace.join("\n")}" }
53
60
  @failed = true
54
61
  end
55
62
  end
56
63
 
57
64
  def show_instrumented_code
58
- puts "\nInstrumented code:\n"
59
- puts format_generated_code(covered_code)
65
+ output { "\nInstrumented code:\n" }
66
+ output { format_generated_code(covered_code) }
60
67
  end
61
68
 
62
69
  def show_ast
63
- puts "\nParsed code:\n"
70
+ output { "\nParsed code:\n" }
64
71
  Node.prepend ColorAST
65
- puts covered_code.covered_ast
72
+ output { covered_code.covered_ast }
66
73
  end
67
74
 
68
75
  def show_char_coverage
69
- puts "\nChar coverage:\n"
76
+ output { "\nChar coverage:\n" }
70
77
 
71
- puts format_char_cover(covered_code, show_whitespace: !!ENV['W'], **options)
78
+ output { format_char_cover(covered_code, show_whitespace: !!ENV['W'], **options) }
72
79
  end
73
80
 
74
81
  def pry
@@ -86,11 +93,29 @@ module DeepCover
86
93
  end
87
94
 
88
95
  def execute
89
- begin
90
- execute_sample(covered_code)
91
- rescue Exception => e
92
- puts "Can't `execute_sample`:#{e.class.name}: #{e}\n#{e.backtrace.join("\n")}"
93
- @failed = true
96
+ execute_sample(covered_code)
97
+ # output { trace_counts } # Keep for low-level debugging purposes
98
+ rescue Exception => e
99
+ output { "Can't `execute_sample`:#{e.class.name}: #{e}\n#{e.backtrace.join("\n")}" }
100
+ @failed = true
101
+ end
102
+
103
+ def trace_counts
104
+ all = []
105
+ trace = TracePoint.new(:call) do |tr|
106
+ if %i[flow_entry_count flow_completion_count execution_count].include? tr.method_id
107
+ node = tr.self
108
+ str = "#{node.type} #{(node.value if node.respond_to?(:value))} #{tr.method_id}"
109
+ all << str unless all.last == str
110
+ end
111
+ end
112
+ trace.enable { covered_code.freeze }
113
+ all
114
+ end
115
+
116
+ def output
117
+ Tools.dont_profile do
118
+ puts yield
94
119
  end
95
120
  end
96
121
  end