deep-cover 0.5.2 → 0.5.3

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 (84) hide show
  1. checksums.yaml +5 -5
  2. data/.deep_cover.rb +8 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +15 -1
  5. data/.travis.yml +1 -0
  6. data/README.md +30 -1
  7. data/Rakefile +10 -1
  8. data/bin/cov +1 -1
  9. data/deep_cover.gemspec +4 -5
  10. data/exe/deep-cover +5 -3
  11. data/lib/deep_cover.rb +1 -1
  12. data/lib/deep_cover/analyser/node.rb +1 -1
  13. data/lib/deep_cover/analyser/ruby25_like_branch.rb +209 -0
  14. data/lib/deep_cover/auto_run.rb +19 -19
  15. data/lib/deep_cover/autoload_tracker.rb +181 -44
  16. data/lib/deep_cover/backports.rb +3 -1
  17. data/lib/deep_cover/base.rb +13 -8
  18. data/lib/deep_cover/basics.rb +1 -1
  19. data/lib/deep_cover/cli/debugger.rb +2 -2
  20. data/lib/deep_cover/cli/instrumented_clone_reporter.rb +21 -8
  21. data/lib/deep_cover/cli/runner.rb +126 -0
  22. data/lib/deep_cover/config_setter.rb +1 -0
  23. data/lib/deep_cover/core_ext/autoload_overrides.rb +82 -14
  24. data/lib/deep_cover/core_ext/coverage_replacement.rb +34 -5
  25. data/lib/deep_cover/core_ext/exec_callbacks.rb +27 -0
  26. data/lib/deep_cover/core_ext/load_overrides.rb +4 -6
  27. data/lib/deep_cover/core_ext/require_overrides.rb +1 -3
  28. data/lib/deep_cover/coverage.rb +105 -2
  29. data/lib/deep_cover/coverage/analysis.rb +30 -28
  30. data/lib/deep_cover/coverage/persistence.rb +60 -70
  31. data/lib/deep_cover/covered_code.rb +16 -49
  32. data/lib/deep_cover/custom_requirer.rb +112 -51
  33. data/lib/deep_cover/load.rb +10 -6
  34. data/lib/deep_cover/memoize.rb +1 -3
  35. data/lib/deep_cover/module_override.rb +7 -0
  36. data/lib/deep_cover/node/assignments.rb +2 -1
  37. data/lib/deep_cover/node/base.rb +6 -6
  38. data/lib/deep_cover/node/block.rb +10 -8
  39. data/lib/deep_cover/node/case.rb +3 -3
  40. data/lib/deep_cover/node/collections.rb +8 -0
  41. data/lib/deep_cover/node/if.rb +19 -3
  42. data/lib/deep_cover/node/literals.rb +28 -7
  43. data/lib/deep_cover/node/mixin/can_augment_children.rb +4 -4
  44. data/lib/deep_cover/node/mixin/child_can_be_empty.rb +1 -1
  45. data/lib/deep_cover/node/mixin/filters.rb +6 -2
  46. data/lib/deep_cover/node/mixin/has_child.rb +8 -8
  47. data/lib/deep_cover/node/mixin/has_child_handler.rb +3 -3
  48. data/lib/deep_cover/node/mixin/has_tracker.rb +7 -3
  49. data/lib/deep_cover/node/root.rb +1 -1
  50. data/lib/deep_cover/node/send.rb +53 -7
  51. data/lib/deep_cover/node/short_circuit.rb +11 -3
  52. data/lib/deep_cover/parser_ext/range.rb +11 -27
  53. data/lib/deep_cover/problem_with_diagnostic.rb +1 -1
  54. data/lib/deep_cover/reporter.rb +0 -1
  55. data/lib/deep_cover/reporter/base.rb +68 -0
  56. data/lib/deep_cover/reporter/html.rb +1 -1
  57. data/lib/deep_cover/reporter/html/index.rb +4 -8
  58. data/lib/deep_cover/reporter/html/site.rb +10 -18
  59. data/lib/deep_cover/reporter/html/source.rb +3 -3
  60. data/lib/deep_cover/reporter/html/template/source.html.erb +1 -1
  61. data/lib/deep_cover/reporter/istanbul.rb +86 -56
  62. data/lib/deep_cover/reporter/text.rb +5 -13
  63. data/lib/deep_cover/reporter/{util/tree.rb → tree/util.rb} +19 -21
  64. data/lib/deep_cover/tools/blank.rb +25 -0
  65. data/lib/deep_cover/tools/builtin_coverage.rb +8 -8
  66. data/lib/deep_cover/tools/dump_covered_code.rb +2 -9
  67. data/lib/deep_cover/tools/execute_sample.rb +17 -6
  68. data/lib/deep_cover/tools/format_generated_code.rb +1 -1
  69. data/lib/deep_cover/tools/indent_string.rb +26 -0
  70. data/lib/deep_cover/tools/our_coverage.rb +2 -2
  71. data/lib/deep_cover/tools/strip_heredoc.rb +18 -0
  72. data/lib/deep_cover/tracker_bucket.rb +50 -0
  73. data/lib/deep_cover/tracker_hits_per_path.rb +35 -0
  74. data/lib/deep_cover/tracker_storage.rb +76 -0
  75. data/lib/deep_cover/tracker_storage_per_path.rb +34 -0
  76. data/lib/deep_cover/version.rb +1 -1
  77. data/lib/deep_cover_entry.rb +3 -0
  78. metadata +30 -37
  79. data/bin/gemcov +0 -8
  80. data/bin/selfcov +0 -21
  81. data/lib/deep_cover/cli/deep_cover.rb +0 -126
  82. data/lib/deep_cover/coverage/base.rb +0 -81
  83. data/lib/deep_cover/coverage/istanbul.rb +0 -34
  84. data/lib/deep_cover/tools/transform_keys.rb +0 -9
@@ -18,8 +18,8 @@ module DeepCover
18
18
  ]
19
19
  end
20
20
 
21
- def branches_summary(of = branches)
22
- of.map do |jump|
21
+ def branches_summary(of_branches = branches)
22
+ of_branches.map do |jump|
23
23
  if jump == conditional
24
24
  'left-hand side'
25
25
  else
@@ -27,8 +27,16 @@ module DeepCover
27
27
  end
28
28
  end.join(' and ')
29
29
  end
30
+
31
+ def operator
32
+ loc_hash[:operator].source.to_sym
33
+ end
30
34
  end
31
35
 
32
- And = Or = ShortCircuit
36
+ class And < ShortCircuit
37
+ end
38
+
39
+ class Or < ShortCircuit
40
+ end
33
41
  end
34
42
  end
@@ -1,37 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Parser::Source::Range
4
- # (1...10).split(2...3, 6...8) => [1...2, 3...6, 7...10]
5
- # Assumes inner_ranges are exclusive, and included in self
6
- def split(*inner_ranges)
7
- inner_ranges.sort_by!(&:begin_pos)
8
- [self.begin, *inner_ranges, self.end]
9
- .each_cons(2)
10
- .map { |i, j| with(begin_pos: i.end_pos, end_pos: j.begin_pos) }
11
- .reject(&:empty?)
4
+ def succ
5
+ adjust(begin_pos: +1, end_pos: +1)
12
6
  end
13
7
 
14
- def lstrip(pattern = /\s*/)
15
- if (match = /^#{pattern}/.match(source))
16
- with(begin_pos: @begin_pos + match[0].length)
17
- else
18
- self
19
- end
8
+ def wrap_rwhitespace
9
+ whitespace = @source_buffer.slice(end_pos..-1)[/\A\s+/] || ''
10
+ adjust(end_pos: whitespace.size)
20
11
  end
21
12
 
22
- def rstrip(pattern = /\s*/)
23
- if (match = /#{pattern}$/.match(source))
24
- with(end_pos: @end_pos - match[0].length)
25
- else
26
- self
13
+ def wrap_rwhitespace_and_comments
14
+ current = wrap_rwhitespace
15
+ while @source_buffer.slice(current.end_pos) == '#'
16
+ comment = @source_buffer.slice(current.end_pos..-1)[/\A[^\n]+/] || ''
17
+ current = current.adjust(end_pos: comment.size).wrap_rwhitespace
27
18
  end
28
- end
29
-
30
- def strip(pattern = /\s*/)
31
- lstrip(pattern).rstrip(pattern)
32
- end
33
-
34
- def succ
35
- adjust(begin_pos: +1, end_pos: +1)
19
+ current
36
20
  end
37
21
  end
@@ -38,7 +38,7 @@ module DeepCover
38
38
  lines.concat(source_lines.map { |line| " #{line}" })
39
39
  if original_exception
40
40
  lines << 'Original exception:'
41
- lines << " #{original_exception.class.name}: #{original_exception.message}"
41
+ lines << " #{original_exception.class}: #{original_exception.message}"
42
42
  backtrace = Tools.truncate_backtrace(original_exception)
43
43
  lines.concat(backtrace.map { |line| " #{line}" })
44
44
  end
@@ -6,6 +6,5 @@ module DeepCover
6
6
  module Reporter
7
7
  end
8
8
  require_relative 'node'
9
- require_relative_dir 'reporter/util'
10
9
  require_relative_dir 'reporter'
11
10
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Reporter
5
+ require_relative 'tree/util'
6
+
7
+ class Base
8
+ include Memoize
9
+ memoize :map, :tree, :root_path
10
+
11
+ attr_reader :options
12
+
13
+ def initialize(coverage, **options)
14
+ @coverage = coverage
15
+ @options = options
16
+ end
17
+
18
+ def analysis
19
+ @analysis ||= Coverage::Analysis.new(@coverage.covered_codes, **options)
20
+ end
21
+
22
+ def each(&block)
23
+ return to_enum :each unless block_given?
24
+ @coverage.each do |covered_code|
25
+ yield relative_path(covered_code.path), covered_code
26
+ end
27
+ self
28
+ end
29
+
30
+ # Same as populate, but also yields data, which is either the analysis data (for leaves)
31
+ # of the sum of the children (for subtrees)
32
+ def populate_stats
33
+ return to_enum(__method__) unless block_given?
34
+ Tree::Util.populate_from_map(
35
+ tree: tree,
36
+ map: map,
37
+ merge: ->(child_data) { Tools.merge(*child_data, :+) }
38
+ ) do |full_path, partial_path, data, children|
39
+ yield relative_path(full_path), relative_path(partial_path), data, children
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def relative_path(path)
46
+ path = path.to_s
47
+ path = path.slice(root_path.length + 1..-1) if path.start_with?(root_path)
48
+ path
49
+ end
50
+
51
+ def root_path
52
+ return '' if tree.size > 1
53
+ path = tree.first.first
54
+ root = File.dirname(path)
55
+ root = File.dirname(root) if File.basename(path) == 'dir'
56
+ root
57
+ end
58
+
59
+ def map
60
+ analysis.stat_map.transform_keys(&:path).transform_keys(&:to_s)
61
+ end
62
+
63
+ def tree
64
+ Tree::Util.paths_to_tree(map.keys)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -8,7 +8,7 @@ module DeepCover
8
8
  module Reporter::HTML
9
9
  class << self
10
10
  def report(coverage, **options)
11
- Site.save(coverage.covered_codes, **options)
11
+ Site.save(coverage, **options)
12
12
  end
13
13
  end
14
14
  end
@@ -4,17 +4,13 @@ module DeepCover
4
4
  require_relative 'base'
5
5
 
6
6
  module Reporter
7
- class HTML::Index < Struct.new(:analysis, :options)
8
- def initialize(analysis, **options)
9
- raise ArgumentError unless analysis.is_a? Coverage::Analysis
10
- super
11
- end
12
-
13
- include Util::Tree
7
+ class HTML::Index < Struct.new(:base)
14
8
  include HTML::Base
9
+ extend Forwardable
10
+ def_delegators :base, :analysis, :options, :populate_stats
15
11
 
16
12
  def stats_to_data
17
- populate_stats(analysis) do |full_path, partial_path, data, children|
13
+ populate_stats do |full_path, partial_path, data, children|
18
14
  data = transform_data(data)
19
15
  if children.empty?
20
16
  {
@@ -1,23 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeepCover
4
+ require_relative 'base'
4
5
  require_relative 'index'
5
6
  require_relative 'source'
6
7
 
7
8
  module Reporter::HTML
8
- class Site < Struct.new(:covered_codes, :options)
9
- def initialize(covered_codes, **options)
10
- raise ArgumentError unless covered_codes.all? { |c| c.is_a? CoveredCode }
11
- super
12
- end
13
-
9
+ class Site < Reporter::Base
14
10
  include Memoize
15
11
  memoize :analysis
16
12
 
17
- def analysis
18
- Coverage::Analysis.new(covered_codes, **options)
19
- end
20
-
21
13
  def path
22
14
  Pathname(options[:output])
23
15
  end
@@ -42,7 +34,7 @@ module DeepCover
42
34
  end
43
35
 
44
36
  def render_index
45
- Tools.render_template(:index, Index.new(analysis, **options))
37
+ Tools.render_template(:index, Index.new(self))
46
38
  end
47
39
 
48
40
  def save_index
@@ -58,20 +50,20 @@ module DeepCover
58
50
  dest.join('deep_cover.css.sass').delete
59
51
  end
60
52
 
61
- def render_source(covered_code)
62
- Tools.render_template(:source, Source.new(analysis.analyser_map.fetch(covered_code)))
53
+ def render_source(partial_path, covered_code)
54
+ Tools.render_template(:source, Source.new(analysis.analyser_map.fetch(covered_code), partial_path))
63
55
  end
64
56
 
65
57
  def save_pages
66
- covered_codes.each do |covered_code|
67
- dest = path.join("#{covered_code.name}.html")
58
+ each do |partial_path, covered_code|
59
+ dest = path.join("#{partial_path}.html")
68
60
  dest.dirname.mkpath
69
- dest.write(render_source(covered_code))
61
+ dest.write(render_source(partial_path, covered_code))
70
62
  end
71
63
  end
72
64
 
73
- def self.save(covered_codes, output:, **options)
74
- Site.new(covered_codes, output: output, **options).save
65
+ def self.save(coverage, output:, **options)
66
+ Site.new(coverage, output: output, **options).save
75
67
  end
76
68
  end
77
69
  end
@@ -4,10 +4,10 @@ module DeepCover
4
4
  module Reporter
5
5
  require_relative 'base'
6
6
 
7
- class HTML::Source < Struct.new(:analyser_map)
7
+ class HTML::Source < Struct.new(:analyser_map, :partial_path)
8
8
  include Tools::Covered
9
9
 
10
- def initialize(analyser_map)
10
+ def initialize(analyser_map, partial_path)
11
11
  raise ArgumentError unless analyser_map.values.all? { |a| a.is_a?(Analyser) }
12
12
  super
13
13
  end
@@ -33,7 +33,7 @@ module DeepCover
33
33
  end
34
34
 
35
35
  def root_path
36
- Pathname('.').relative_path_from(Pathname(covered_code.name).dirname)
36
+ Pathname('.').relative_path_from(Pathname(partial_path).dirname)
37
37
  end
38
38
 
39
39
  def stats
@@ -19,7 +19,7 @@
19
19
  </div>
20
20
  <div class="nav">
21
21
  <a href="index.html" aria-label="Home"><i class="fa fa-home" aria-hidden="true"></i></a>
22
- <span class="path"><%= covered_code.name %><span>
22
+ <span class="path"><%= partial_path %><span>
23
23
  </div>
24
24
  </div>
25
25
  </header>
@@ -4,8 +4,7 @@ require 'json'
4
4
 
5
5
  module DeepCover
6
6
  module Reporter
7
- class Istanbul < Struct.new(:covered_code, :options)
8
- # Converters has no dependency on the including class.
7
+ class Istanbul < Base
9
8
  module Converters
10
9
  def convert_range(range)
11
10
  {start: {
@@ -68,83 +67,114 @@ module DeepCover
68
67
  }
69
68
  end
70
69
  end
71
- include Converters
72
70
 
73
- def node_analyser
74
- @node_analyser ||= Analyser::Node.new(covered_code, **options)
75
- end
71
+ class CoveredCodeConverter < Struct.new(:covered_code, :options)
72
+ include Converters
76
73
 
77
- def node_runs
78
- @node_runs ||= node_analyser.results
79
- end
74
+ def node_analyser
75
+ @node_analyser ||= Analyser::Node.new(covered_code, **options)
76
+ end
80
77
 
81
- def functions
82
- @functions ||= Analyser::Function.new(node_analyser, **options).results
83
- end
78
+ def node_runs
79
+ @node_runs ||= node_analyser.results
80
+ end
84
81
 
85
- def statements
86
- @statements ||= Analyser::Statement.new(node_analyser, **options).results
87
- end
82
+ def functions
83
+ @functions ||= Analyser::Function.new(node_analyser, **options).results
84
+ end
88
85
 
89
- def branches
90
- @branches ||= Analyser::Branch.new(node_analyser, **options).results
91
- end
86
+ def statements
87
+ @statements ||= Analyser::Statement.new(node_analyser, **options).results
88
+ end
92
89
 
93
- def branch_map
94
- branches.map do |node, branches_runs|
95
- convert_branch(node, branches_runs.keys)
90
+ def branches
91
+ @branches ||= Analyser::Branch.new(node_analyser, **options).results
96
92
  end
97
- end
98
93
 
99
- # Istanbul doesn't understand how to ignore a branch...
100
- def zero_to_something(values)
101
- values.map { |v| v || 1 }
102
- end
94
+ def branch_map
95
+ branches.map do |node, branches_runs|
96
+ convert_branch(node, branches_runs.keys)
97
+ end
98
+ end
103
99
 
104
- def branch_runs
105
- branches.values.map { |r| zero_to_something(r.values) }
106
- end
100
+ # Istanbul doesn't understand how to ignore a branch...
101
+ def zero_to_something(values)
102
+ values.map { |v| v || 1 }
103
+ end
107
104
 
108
- def statement_map
109
- statements.keys.map { |range| convert_range(range) }
110
- end
105
+ def branch_runs
106
+ branches.values.map { |r| zero_to_something(r.values) }
107
+ end
111
108
 
112
- def statement_runs
113
- statements.values
114
- end
109
+ def statement_map
110
+ statements.keys.map { |range| convert_range(range) }
111
+ end
115
112
 
116
- def function_map
117
- functions.keys.map { |n| convert_function(n) }
118
- end
113
+ def statement_runs
114
+ statements.values
115
+ end
119
116
 
120
- def function_runs
121
- functions.values
122
- end
117
+ def function_map
118
+ functions.keys.map { |n| convert_function(n) }
119
+ end
120
+
121
+ def function_runs
122
+ functions.values
123
+ end
123
124
 
124
- def data
125
- {
126
- statementMap: statement_map,
127
- s: statement_runs,
128
- fnMap: function_map,
129
- f: function_runs,
130
- branchMap: branch_map,
131
- b: branch_runs,
132
- }
125
+ def data
126
+ {
127
+ statementMap: statement_map,
128
+ s: statement_runs,
129
+ fnMap: function_map,
130
+ f: function_runs,
131
+ branchMap: branch_map,
132
+ b: branch_runs,
133
+ }
134
+ end
135
+
136
+ def convert
137
+ {
138
+ path: covered_code.path,
139
+ **data.transform_values { |l| convert_list(l) },
140
+ }
141
+ end
133
142
  end
134
143
 
135
144
  def convert
136
- {covered_code.name => {
137
- path: covered_code.path,
138
- **data.transform_values { |l| convert_list(l) },
139
- },
140
- }
145
+ each.to_a.to_h.transform_values do |covered_code|
146
+ CoveredCodeConverter.new(covered_code, **@options).convert
147
+ end
148
+ end
149
+
150
+ def save(dir: '.', name: '.nyc_output')
151
+ path = Pathname.new(dir).expand_path.join(name)
152
+ path.mkpath
153
+ path.each_child(&:delete)
154
+ path.join('deep_cover.json').write(JSON.pretty_generate(convert))
155
+ path
141
156
  end
142
157
 
143
158
  def report
144
- convert.to_json
159
+ output = @options[:output]
160
+ dir = save.dirname
161
+ unless [nil, false, '', 'false'].include? output
162
+ output = File.expand_path(output)
163
+ html = "--reporter=html --report-dir='#{output}'"
164
+ if @options[:open]
165
+ html += " && open '#{output}/index.html'"
166
+ else
167
+ msg = "\nHTML coverage written to: '#{output}/index.html'"
168
+ end
169
+ end
170
+ `cd #{dir} && nyc report --reporter=text #{html}` + msg.to_s
145
171
  end
146
172
 
147
173
  class << self
174
+ def report(coverage, **options)
175
+ new(coverage, options).report
176
+ end
177
+
148
178
  def available?
149
179
  `nyc --version` >= '11.' rescue false
150
180
  end