deep-cover-core 0.6.3.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.rspec_all +3 -0
  4. data/.rubocop.yml +1 -0
  5. data/Gemfile +11 -0
  6. data/deep_cover_core.gemspec +46 -0
  7. data/lib/deep-cover.rb +3 -0
  8. data/lib/deep_cover/analyser/base.rb +104 -0
  9. data/lib/deep_cover/analyser/branch.rb +41 -0
  10. data/lib/deep_cover/analyser/covered_code_source.rb +21 -0
  11. data/lib/deep_cover/analyser/function.rb +14 -0
  12. data/lib/deep_cover/analyser/node.rb +54 -0
  13. data/lib/deep_cover/analyser/per_char.rb +38 -0
  14. data/lib/deep_cover/analyser/per_line.rb +41 -0
  15. data/lib/deep_cover/analyser/ruby25_like_branch.rb +211 -0
  16. data/lib/deep_cover/analyser/statement.rb +33 -0
  17. data/lib/deep_cover/analyser/stats.rb +54 -0
  18. data/lib/deep_cover/analyser/subset.rb +27 -0
  19. data/lib/deep_cover/analyser.rb +23 -0
  20. data/lib/deep_cover/auto_run.rb +71 -0
  21. data/lib/deep_cover/autoload_tracker.rb +215 -0
  22. data/lib/deep_cover/backports.rb +22 -0
  23. data/lib/deep_cover/base.rb +117 -0
  24. data/lib/deep_cover/basics.rb +22 -0
  25. data/lib/deep_cover/builtin_takeover.rb +10 -0
  26. data/lib/deep_cover/config.rb +104 -0
  27. data/lib/deep_cover/config_setter.rb +33 -0
  28. data/lib/deep_cover/core_ext/autoload_overrides.rb +112 -0
  29. data/lib/deep_cover/core_ext/coverage_replacement.rb +61 -0
  30. data/lib/deep_cover/core_ext/exec_callbacks.rb +27 -0
  31. data/lib/deep_cover/core_ext/instruction_sequence_load_iseq.rb +32 -0
  32. data/lib/deep_cover/core_ext/load_overrides.rb +19 -0
  33. data/lib/deep_cover/core_ext/require_overrides.rb +28 -0
  34. data/lib/deep_cover/coverage/analysis.rb +42 -0
  35. data/lib/deep_cover/coverage/persistence.rb +84 -0
  36. data/lib/deep_cover/coverage.rb +125 -0
  37. data/lib/deep_cover/covered_code.rb +145 -0
  38. data/lib/deep_cover/custom_requirer.rb +187 -0
  39. data/lib/deep_cover/flag_comment_associator.rb +68 -0
  40. data/lib/deep_cover/load.rb +66 -0
  41. data/lib/deep_cover/memoize.rb +48 -0
  42. data/lib/deep_cover/module_override.rb +39 -0
  43. data/lib/deep_cover/node/arguments.rb +51 -0
  44. data/lib/deep_cover/node/assignments.rb +273 -0
  45. data/lib/deep_cover/node/base.rb +155 -0
  46. data/lib/deep_cover/node/begin.rb +27 -0
  47. data/lib/deep_cover/node/block.rb +61 -0
  48. data/lib/deep_cover/node/branch.rb +32 -0
  49. data/lib/deep_cover/node/case.rb +113 -0
  50. data/lib/deep_cover/node/collections.rb +31 -0
  51. data/lib/deep_cover/node/const.rb +12 -0
  52. data/lib/deep_cover/node/def.rb +40 -0
  53. data/lib/deep_cover/node/empty_body.rb +32 -0
  54. data/lib/deep_cover/node/exceptions.rb +79 -0
  55. data/lib/deep_cover/node/if.rb +73 -0
  56. data/lib/deep_cover/node/keywords.rb +86 -0
  57. data/lib/deep_cover/node/literals.rb +100 -0
  58. data/lib/deep_cover/node/loops.rb +74 -0
  59. data/lib/deep_cover/node/mixin/can_augment_children.rb +65 -0
  60. data/lib/deep_cover/node/mixin/check_completion.rb +18 -0
  61. data/lib/deep_cover/node/mixin/child_can_be_empty.rb +27 -0
  62. data/lib/deep_cover/node/mixin/executed_after_children.rb +15 -0
  63. data/lib/deep_cover/node/mixin/execution_location.rb +66 -0
  64. data/lib/deep_cover/node/mixin/filters.rb +47 -0
  65. data/lib/deep_cover/node/mixin/flow_accounting.rb +71 -0
  66. data/lib/deep_cover/node/mixin/has_child.rb +145 -0
  67. data/lib/deep_cover/node/mixin/has_child_handler.rb +75 -0
  68. data/lib/deep_cover/node/mixin/has_tracker.rb +46 -0
  69. data/lib/deep_cover/node/mixin/is_statement.rb +20 -0
  70. data/lib/deep_cover/node/mixin/rewriting.rb +35 -0
  71. data/lib/deep_cover/node/mixin/wrapper.rb +15 -0
  72. data/lib/deep_cover/node/module.rb +66 -0
  73. data/lib/deep_cover/node/root.rb +20 -0
  74. data/lib/deep_cover/node/send.rb +161 -0
  75. data/lib/deep_cover/node/short_circuit.rb +42 -0
  76. data/lib/deep_cover/node/splat.rb +15 -0
  77. data/lib/deep_cover/node/variables.rb +16 -0
  78. data/lib/deep_cover/node.rb +23 -0
  79. data/lib/deep_cover/parser_ext/range.rb +21 -0
  80. data/lib/deep_cover/problem_with_diagnostic.rb +63 -0
  81. data/lib/deep_cover/reporter/base.rb +68 -0
  82. data/lib/deep_cover/reporter/html/base.rb +14 -0
  83. data/lib/deep_cover/reporter/html/index.rb +59 -0
  84. data/lib/deep_cover/reporter/html/site.rb +68 -0
  85. data/lib/deep_cover/reporter/html/source.rb +136 -0
  86. data/lib/deep_cover/reporter/html/template/assets/32px.png +0 -0
  87. data/lib/deep_cover/reporter/html/template/assets/40px.png +0 -0
  88. data/lib/deep_cover/reporter/html/template/assets/deep_cover.css +291 -0
  89. data/lib/deep_cover/reporter/html/template/assets/deep_cover.css.sass +336 -0
  90. data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.js +4 -0
  91. data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.map +1 -0
  92. data/lib/deep_cover/reporter/html/template/assets/jstree.css +1108 -0
  93. data/lib/deep_cover/reporter/html/template/assets/jstree.js +8424 -0
  94. data/lib/deep_cover/reporter/html/template/assets/jstreetable.js +1069 -0
  95. data/lib/deep_cover/reporter/html/template/assets/throbber.gif +0 -0
  96. data/lib/deep_cover/reporter/html/template/index.html.erb +75 -0
  97. data/lib/deep_cover/reporter/html/template/source.html.erb +35 -0
  98. data/lib/deep_cover/reporter/html.rb +15 -0
  99. data/lib/deep_cover/reporter/istanbul.rb +184 -0
  100. data/lib/deep_cover/reporter/text.rb +58 -0
  101. data/lib/deep_cover/reporter/tree/util.rb +86 -0
  102. data/lib/deep_cover/reporter.rb +10 -0
  103. data/lib/deep_cover/tools/blank.rb +25 -0
  104. data/lib/deep_cover/tools/builtin_coverage.rb +55 -0
  105. data/lib/deep_cover/tools/camelize.rb +13 -0
  106. data/lib/deep_cover/tools/content_tag.rb +11 -0
  107. data/lib/deep_cover/tools/covered.rb +9 -0
  108. data/lib/deep_cover/tools/execute_sample.rb +34 -0
  109. data/lib/deep_cover/tools/format.rb +18 -0
  110. data/lib/deep_cover/tools/format_char_cover.rb +19 -0
  111. data/lib/deep_cover/tools/format_generated_code.rb +27 -0
  112. data/lib/deep_cover/tools/indent_string.rb +26 -0
  113. data/lib/deep_cover/tools/merge.rb +16 -0
  114. data/lib/deep_cover/tools/number_lines.rb +22 -0
  115. data/lib/deep_cover/tools/our_coverage.rb +11 -0
  116. data/lib/deep_cover/tools/profiling.rb +68 -0
  117. data/lib/deep_cover/tools/render_template.rb +13 -0
  118. data/lib/deep_cover/tools/require_relative_dir.rb +12 -0
  119. data/lib/deep_cover/tools/scan_match_datas.rb +10 -0
  120. data/lib/deep_cover/tools/silence_warnings.rb +18 -0
  121. data/lib/deep_cover/tools/slice.rb +9 -0
  122. data/lib/deep_cover/tools/strip_heredoc.rb +18 -0
  123. data/lib/deep_cover/tools/truncate_backtrace.rb +32 -0
  124. data/lib/deep_cover/tools.rb +22 -0
  125. data/lib/deep_cover/tracker_bucket.rb +50 -0
  126. data/lib/deep_cover/tracker_hits_per_path.rb +35 -0
  127. data/lib/deep_cover/tracker_storage.rb +76 -0
  128. data/lib/deep_cover/tracker_storage_per_path.rb +34 -0
  129. data/lib/deep_cover/version.rb +5 -0
  130. data/lib/deep_cover.rb +22 -0
  131. metadata +329 -0
@@ -0,0 +1,75 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <script src="assets/jquery-3.2.1.min.js"></script>
5
+ <script src="assets/jstree.js"></script>
6
+ <script src="assets/jstreetable.js"></script>
7
+ <script src="https://use.fontawesome.com/81e98abb93.js"></script>
8
+ <link href="assets/jstree.css" rel="stylesheet">
9
+ <link href="assets/deep_cover.css" rel="stylesheet">
10
+
11
+ <script>
12
+ window.DeepCover = {
13
+ index: <%= stats_to_data.to_json %>,
14
+ columns: <%= columns.to_json %>
15
+ };
16
+ var numColumns = {
17
+ width: 100,
18
+ sort: function(a, b) {
19
+ return (a.executed + a.ignored > b.executed + b.ignored ? 1 : -1);
20
+ },
21
+ format: function(stats) {
22
+ var ok = stats.executed + stats.ignored;
23
+ return '' + ok + '/' + (ok + stats.not_executed);
24
+ }
25
+ };
26
+ var percentColumns = {
27
+ width: 60,
28
+ format: function(percent) {
29
+ var decile = Math.floor(percent / 10);
30
+ return '<span class="percent-' + decile + '0">' + percent + '</span>';
31
+ }
32
+ }
33
+ for(var i = 1; i < DeepCover.columns.length;) {
34
+ $.extend(DeepCover.columns[i++], numColumns);
35
+ $.extend(DeepCover.columns[i++], percentColumns);
36
+ }
37
+ </script>
38
+
39
+ <script>
40
+ $(function() {
41
+ $(".tree").jstree({
42
+ plugins: ["table","sort"],
43
+ core: {
44
+ data: DeepCover.index
45
+ },
46
+ // configure tree table
47
+ table: {
48
+ columns: DeepCover.columns,
49
+ resizable: true,
50
+ width: "100%",
51
+ height: "100%"
52
+ }
53
+ });
54
+ });
55
+ </script>
56
+ </head>
57
+ <body class="index">
58
+ <header>
59
+ <div class="content">
60
+ <div class="info"><span class="deep-cover"><span class="deep">Deep</span>Cover</span>
61
+ <span class="version">v<%= DeepCover::VERSION %></span>
62
+ </div>
63
+ </div>
64
+ </header>
65
+ <main>
66
+ <div class="tree"></div>
67
+ </main>
68
+ <footer>
69
+ <div class="details">
70
+ <span class="date"><%= Time.now %></span>
71
+ <span class="setup"><%= setup %></span>
72
+ </div>
73
+ </footer>
74
+ </body>
75
+ </html>
@@ -0,0 +1,35 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <base href="<%= root_path %>"/>
5
+ <script src="assets/jquery-3.2.1.min.js"></script>
6
+ <script src="https://use.fontawesome.com/81e98abb93.js"></script>
7
+ <link rel="stylesheet" href="assets/deep_cover.css" type="text/css"/>
8
+ </head>
9
+ <body class="source">
10
+ <div class="overlay">
11
+ <div class="center">
12
+ <div class="stats"><%= stats %></div>
13
+ </div>
14
+ </div>
15
+ <header>
16
+ <div class="content">
17
+ <div class="info"><span class="deep-cover"><span class="deep">Deep</span>Cover</span>
18
+ <span class="version">v<%= DeepCover::VERSION %></span>
19
+ </div>
20
+ <div class="nav">
21
+ <a href="index.html" aria-label="Home"><i class="fa fa-home" aria-hidden="true"></i></a>
22
+ <span class="path"><%= partial_path %><span>
23
+ </div>
24
+ </div>
25
+ </header>
26
+ <main class="source"><%= format_source %></main>
27
+ <script>
28
+ $(function() {
29
+ $('.stats .branch').click(function() {
30
+ $('body').toggleClass('show-forks')
31
+ });
32
+ });
33
+ </script>
34
+ </body>
35
+ </html>
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ Reporter::HTML = Module.new
5
+
6
+ require_relative_dir 'html'
7
+
8
+ module Reporter::HTML
9
+ class << self
10
+ def report(coverage, **options)
11
+ Site.save(coverage, **options)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module DeepCover
6
+ module Reporter
7
+ class Istanbul < Base
8
+ module Converters
9
+ def convert_range(range)
10
+ {start: {
11
+ line: range.line,
12
+ column: range.column,
13
+ },
14
+ end: {
15
+ line: range.last_line,
16
+ column: range.last_column - 1, # Our ranges are exclusive, Istanbul's are inclusive
17
+ },
18
+ }
19
+ end
20
+
21
+ # [:a, :b, :c] => {'1': :a, '2': :b, '3': :c}
22
+ def convert_list(list)
23
+ list.map.with_index { |val, i| [i.succ.to_s, val] }.to_h
24
+ end
25
+
26
+ def convert_def(node)
27
+ ends_at = node.signature.loc_hash[:end] || node.loc_hash[:name]
28
+ decl = node.loc_hash[:keyword].with(end_pos: ends_at.end_pos)
29
+ _convert_function(node, node.method_name, decl)
30
+ end
31
+
32
+ def convert_block(node)
33
+ decl = node.loc_hash[:begin]
34
+ if (args = node.args.expression)
35
+ decl = decl.join(args)
36
+ end
37
+ _convert_function(node, '(block)', decl)
38
+ end
39
+
40
+ def convert_function(node)
41
+ if node.is_a?(Node::Block)
42
+ convert_block(node)
43
+ else
44
+ convert_def(node)
45
+ end
46
+ end
47
+
48
+ def convert_branch(node, branches = node.branches)
49
+ # Currently, nyc seems to outputs the same location over and over...
50
+ {
51
+ loc: convert_range(node.expression),
52
+ type: node.type,
53
+ line: node.expression.line,
54
+ locations: branches.map { |n| convert_range(n.expression || node.expression) },
55
+ }
56
+ end
57
+
58
+ private
59
+
60
+ def _convert_function(node, name, decl)
61
+ loc = node.body ? node.body.expression : decl.end
62
+ {
63
+ name: name,
64
+ line: node.expression.line,
65
+ decl: convert_range(decl),
66
+ loc: convert_range(loc),
67
+ }
68
+ end
69
+ end
70
+
71
+ class CoveredCodeConverter < Struct.new(:covered_code, :options)
72
+ include Converters
73
+
74
+ def node_analyser
75
+ @node_analyser ||= Analyser::Node.new(covered_code, **options)
76
+ end
77
+
78
+ def node_runs
79
+ @node_runs ||= node_analyser.results
80
+ end
81
+
82
+ def functions
83
+ @functions ||= Analyser::Function.new(node_analyser, **options).results
84
+ end
85
+
86
+ def statements
87
+ @statements ||= Analyser::Statement.new(node_analyser, **options).results
88
+ end
89
+
90
+ def branches
91
+ @branches ||= Analyser::Branch.new(node_analyser, **options).results
92
+ end
93
+
94
+ def branch_map
95
+ branches.map do |node, branches_runs|
96
+ convert_branch(node, branches_runs.keys)
97
+ end
98
+ end
99
+
100
+ # Istanbul doesn't understand how to ignore a branch...
101
+ def zero_to_something(values)
102
+ values.map { |v| v || 1 }
103
+ end
104
+
105
+ def branch_runs
106
+ branches.values.map { |r| zero_to_something(r.values) }
107
+ end
108
+
109
+ def statement_map
110
+ statements.keys.map { |range| convert_range(range) }
111
+ end
112
+
113
+ def statement_runs
114
+ statements.values
115
+ end
116
+
117
+ def function_map
118
+ functions.keys.map { |n| convert_function(n) }
119
+ end
120
+
121
+ def function_runs
122
+ functions.values
123
+ end
124
+
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
142
+ end
143
+
144
+ def convert
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
156
+ end
157
+
158
+ def report
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
171
+ end
172
+
173
+ class << self
174
+ def report(coverage, **options)
175
+ new(coverage, options).report
176
+ end
177
+
178
+ def available?
179
+ `nyc --version` >= '11.' rescue false
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ require_relative 'base'
5
+
6
+ module Reporter
7
+ class Text < Base
8
+ INDENT = ' '
9
+ def report
10
+ formatted_headings = headings.map.with_index { |h, i| {value: h, alignment: :center} }
11
+ columns = rows.transpose
12
+ (1...columns.size).step(2) { |i| columns[i] = formatted_stats(columns[i]) }
13
+ table = Terminal::Table.new(
14
+ headings: formatted_headings,
15
+ rows: columns.transpose,
16
+ style: {border_bottom: false, border_top: false, alignment: :right},
17
+ )
18
+ table.align_column 0, :left
19
+ table.render + "\n\nOverall: #{analysis.overall}%"
20
+ end
21
+
22
+ def self.report(coverage, **options)
23
+ Text.new(coverage, **options).report
24
+ end
25
+
26
+ private
27
+
28
+ def formatted_stats(data)
29
+ columns = data.transpose
30
+ columns[1..1] = [] if columns[1].none?
31
+ Terminal::Table.new(
32
+ rows: columns.transpose,
33
+ style: {border_x: '', border_bottom: false, border_top: false, alignment: :right}
34
+ ).render.gsub(' | ', ' ').gsub(/ ?\| ?/, '').split("\n")
35
+ end
36
+
37
+ def rows
38
+ populate_stats.map do |full_path, partial_path, data, children|
39
+ [partial_path, *transform_data(data)]
40
+ end
41
+ end
42
+
43
+ def headings
44
+ Coverage::Analysis.template.values.flat_map do |analyser|
45
+ [analyser.human_name, '%']
46
+ end.unshift('Path')
47
+ end
48
+
49
+ # {per_char: Stat, ...} => ['1 [+2] / 3', '100 %', ...]
50
+ def transform_data(data)
51
+ data.flat_map do |type, stat|
52
+ ignored = "[+#{stat.ignored}]" if stat.ignored > 0
53
+ [[stat.executed, ignored, '/', stat.potentially_executable], format('%.2f', stat.percent_covered)]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Reporter
5
+ class Tree
6
+ # Utility functions to deal with trees
7
+ module Util
8
+ extend self
9
+
10
+ def populate_from_map(tree:, map:, merge:)
11
+ return to_enum(__method__, tree: tree, map: map, merge: merge) unless block_given?
12
+ final_results, _final_data = populate(tree) do |full_path, partial_path, children|
13
+ if children.empty?
14
+ data = map.fetch(full_path)
15
+ else
16
+ child_results, child_data = children.transpose
17
+ data = merge.call(child_data)
18
+ end
19
+ result = yield full_path, partial_path, data, child_results || []
20
+ [result, data]
21
+ end.transpose
22
+ final_results
23
+ end
24
+
25
+ def paths_to_tree(paths)
26
+ twigs = paths.map do |path|
27
+ partials = path_to_partial_paths(path)
28
+ list_to_twig(partials)
29
+ end
30
+ tree = deep_merge(twigs)
31
+ simplify(tree)
32
+ end
33
+
34
+ # 'some/example/path' => %w[some example path]
35
+ def path_to_partial_paths(path)
36
+ path.to_s.split('/')
37
+ end
38
+
39
+ # A twig is a tree with only single branches
40
+ # [a, b, c] =>
41
+ # {a: {b: {c: {} } } }
42
+ def list_to_twig(items)
43
+ result = {}
44
+ items.inject(result) do |parent, value|
45
+ parent[value] = {}
46
+ end
47
+ result
48
+ end
49
+
50
+ # [{a: {b: {c: {} } } }
51
+ # {a: {b: {d: {} } } }]
52
+ # => {a: {b: {c: {}, d: {} }}}
53
+ def deep_merge(trees)
54
+ trees.inject({}) do |result, h|
55
+ result.merge(h) { |k, val, val_b| deep_merge([val, val_b]) }
56
+ end
57
+ end
58
+
59
+ # {a: {b: {c: {}, d: {} }}}
60
+ # => {a/b: {c: {}, d: {} }}
61
+ def simplify(tree)
62
+ tree.map do |key, sub_tree|
63
+ sub_tree = simplify(sub_tree)
64
+ if sub_tree.size == 1
65
+ key2, sub_tree = sub_tree.first
66
+ key = "#{key}/#{key2}"
67
+ end
68
+ [key, sub_tree]
69
+ end.to_h
70
+ end
71
+
72
+ # {a: {b: {}}} => [ra, rb]
73
+ # where rb = yield('a/b', 'b', [])
74
+ # and ra = yield('a', 'a', [rb])
75
+ def populate(tree, dir = '', &block)
76
+ return to_enum(__method__, tree, dir) unless block_given?
77
+ tree.map do |path, children_hash|
78
+ full_path = [dir, path].join
79
+ children = populate(children_hash, "#{full_path}/", &block)
80
+ yield full_path, path, children
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ bootstrap
5
+
6
+ module Reporter
7
+ end
8
+ require_relative 'node'
9
+ require_relative_dir 'reporter'
10
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Tools::Blank
5
+ BLANK_RE = /\A[[:space:]]*\z/
6
+
7
+ # Homemade poor-man's blank?
8
+ # Based, but modified, on https://github.com/rails/rails/blob/5-0-stable/activesupport/lib/active_support/core_ext/object/blank.rb
9
+ def blank?(obj)
10
+ if obj.is_a?(String)
11
+ obj.empty? || obj =~ BLANK_RE
12
+ else
13
+ obj.respond_to?(:empty?) ? !!obj.empty? : !obj
14
+ end
15
+ end
16
+
17
+ def present?(obj)
18
+ !blank?(obj)
19
+ end
20
+
21
+ def presence(obj)
22
+ obj if present?(obj)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Tools::BuiltinCoverage
5
+ def builtin_coverage(source, filename, lineno)
6
+ require 'coverage'
7
+ filename = File.absolute_path(File.expand_path(filename))
8
+ ::Coverage.start
9
+ begin
10
+ Tools.silence_warnings do
11
+ execute_sample -> { run_with_line_coverage(source, filename, lineno) }
12
+ end
13
+ ensure
14
+ result = ::Coverage.result
15
+ end
16
+ unshift_coverage(result.fetch(filename), lineno)
17
+ end
18
+
19
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
20
+ # Executes the source as if it was in the specified file while
21
+ # builtin coverage information is still captured
22
+ def run_with_line_coverage(source, filename = nil, lineno = 1)
23
+ source = shift_source(source, lineno)
24
+ Object.to_java.getRuntime.executeScript(source, filename)
25
+ end
26
+ else
27
+ # In ruby 2.0 and 2.1, using 2, 3 or 4 as lineno with RubyVM::InstructionSequence.compile
28
+ # will cause the coverage result to be truncated.
29
+ # 1: [1,2,nil,1]
30
+ # 2: [nil,1,2,nil]
31
+ # 3: [nil,nil,1,2]
32
+ # 4: [nil,nil,nil,1]
33
+ # 5: [nil,nil,nil,nil,1,2,nil,1]
34
+ # Using 1 and 5 or more do not seem to show this issue.
35
+ # The workaround is to create the fake lines manually and always use the default lineno
36
+
37
+ # Executes the source as if it was in the specified file while
38
+ # builtin coverage information is still captured
39
+ def run_with_line_coverage(source, filename = nil, lineno = 1)
40
+ source = shift_source(source, lineno)
41
+ RubyVM::InstructionSequence.compile(source, filename).eval
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def shift_source(source, lineno)
48
+ "\n" * (lineno - 1) + source
49
+ end
50
+
51
+ def unshift_coverage(coverage, lineno)
52
+ coverage[(lineno - 1)..-1]
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Tools
5
+ module Camelize
6
+ extend self # Loaded before bootstrap
7
+ # Poor man's camelize. 'an_example' => 'AnExample'
8
+ def camelize(string)
9
+ string.to_s.gsub(/([a-z\d]*)[_?!]?/) { Regexp.last_match(1).capitalize }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Tools::ContentTag
5
+ # Poor man's content_tag. No HTML escaping included
6
+ def content_tag(tag, content, **options)
7
+ attrs = options.map { |kind, value| %{ #{kind}="#{value}"} }.join
8
+ "<#{tag}#{attrs}>#{content}</#{tag}>"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Tools::Covered
5
+ def covered?(runs)
6
+ runs && runs > 0
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Tools::ExecuteSample
5
+ class ExceptionInSample < StandardError
6
+ end
7
+
8
+ # Returns true if the code would have continued, false if the rescue was triggered.
9
+ def execute_sample(to_execute, source: nil)
10
+ # Disable some annoying warning by ruby. We are testing edge cases, so warnings are to be expected.
11
+ Tools.silence_warnings do
12
+ if to_execute.is_a?(CoveredCode)
13
+ to_execute.execute_code
14
+ else
15
+ to_execute.call
16
+ end
17
+ end
18
+ true
19
+ rescue StandardError => e
20
+ # In our samples, a simple `raise` is expected and doesn't need to be rescued
21
+ return false if e.is_a?(RuntimeError) && e.message.empty?
22
+
23
+ source = to_execute.covered_source if to_execute.is_a?(CoveredCode)
24
+ raise unless source
25
+
26
+ inner_msg = Tools.indent_string("#{e.class.name}: #{e.message}", 4)
27
+ source = Tools.indent_string(source, 4)
28
+ msg = "Exception when executing the sample:\n#{inner_msg}\n*Code follows*\n#{source}"
29
+ new_exc = ExceptionInSample.new(msg)
30
+ new_exc.set_backtrace(e.backtrace)
31
+ raise new_exc
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Tools::Format
5
+ CONVERT = Hash.new(' ')
6
+ CONVERT[0] = 'x '
7
+ CONVERT[nil] = '- '
8
+
9
+ def format(*results, filename: nil, source: nil)
10
+ source ||= File.read(filename)
11
+ results.map! { |counts| counts.map { |c| CONVERT[c] } }
12
+ [*results, source.lines].transpose.map do |parts|
13
+ *line_results, line = parts
14
+ Term::ANSIColor.white(line_results.join) + line.to_s
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Tools::FormatCharCover
5
+ COLOR = {'x' => :red, ' ' => :green, '-' => :faint}.freeze
6
+ WHITESPACE_MAP = Hash.new { |_, v| v }.merge!(' ' => '·', "\t" => '→ ')
7
+ def format_char_cover(covered_code, show_whitespace: false, **options)
8
+ bc = covered_code.char_cover(**options)
9
+ covered_code.buffer.source_lines.map.with_index do |line, line_index|
10
+ next line if line.strip =~ /^#[ >]/
11
+ line.chars.map.with_index do |c, c_index|
12
+ color = COLOR[bc[line_index][c_index]]
13
+ c = WHITESPACE_MAP[c] if show_whitespace
14
+ Term::ANSIColor.send(color, c)
15
+ end.join
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Tools::FormatGeneratedCode
5
+ def format_generated_code(covered_code)
6
+ inserts = []
7
+ generated_code = covered_code.instrument_source do |inserted, _node, expr_limit|
8
+ inserts << [expr_limit, inserted.size]
9
+ Term::ANSIColor.yellow(inserted)
10
+ end
11
+
12
+ inserts = inserts.sort_by { |exp, _| [exp.line, exp.column] }.reverse
13
+ generated_lines = generated_code.split("\n")
14
+
15
+ inserts.each do |exp_limit, size|
16
+ # Line index starts at 1, so array index returns the next line
17
+ comment_line = generated_lines[exp_limit.line]
18
+ next if Tools.blank?(comment_line)
19
+ next unless comment_line.start_with?('#>')
20
+ next if comment_line.start_with?('#>X')
21
+ next unless comment_line.size >= exp_limit.column
22
+ comment_line.insert(exp_limit.column, ' ' * size)
23
+ end
24
+ generated_lines.join("\n")
25
+ end
26
+ end
27
+ end