deep-cover 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
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