deep-cover 0.1.14 → 0.1.15

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