deep-cover-core 0.6.3.pre

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