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.
- checksums.yaml +7 -0
- data/.rspec +4 -0
- data/.rspec_all +3 -0
- data/.rubocop.yml +1 -0
- data/Gemfile +11 -0
- data/deep_cover_core.gemspec +46 -0
- data/lib/deep-cover.rb +3 -0
- data/lib/deep_cover/analyser/base.rb +104 -0
- data/lib/deep_cover/analyser/branch.rb +41 -0
- data/lib/deep_cover/analyser/covered_code_source.rb +21 -0
- data/lib/deep_cover/analyser/function.rb +14 -0
- data/lib/deep_cover/analyser/node.rb +54 -0
- data/lib/deep_cover/analyser/per_char.rb +38 -0
- data/lib/deep_cover/analyser/per_line.rb +41 -0
- data/lib/deep_cover/analyser/ruby25_like_branch.rb +211 -0
- data/lib/deep_cover/analyser/statement.rb +33 -0
- data/lib/deep_cover/analyser/stats.rb +54 -0
- data/lib/deep_cover/analyser/subset.rb +27 -0
- data/lib/deep_cover/analyser.rb +23 -0
- data/lib/deep_cover/auto_run.rb +71 -0
- data/lib/deep_cover/autoload_tracker.rb +215 -0
- data/lib/deep_cover/backports.rb +22 -0
- data/lib/deep_cover/base.rb +117 -0
- data/lib/deep_cover/basics.rb +22 -0
- data/lib/deep_cover/builtin_takeover.rb +10 -0
- data/lib/deep_cover/config.rb +104 -0
- data/lib/deep_cover/config_setter.rb +33 -0
- data/lib/deep_cover/core_ext/autoload_overrides.rb +112 -0
- data/lib/deep_cover/core_ext/coverage_replacement.rb +61 -0
- data/lib/deep_cover/core_ext/exec_callbacks.rb +27 -0
- data/lib/deep_cover/core_ext/instruction_sequence_load_iseq.rb +32 -0
- data/lib/deep_cover/core_ext/load_overrides.rb +19 -0
- data/lib/deep_cover/core_ext/require_overrides.rb +28 -0
- data/lib/deep_cover/coverage/analysis.rb +42 -0
- data/lib/deep_cover/coverage/persistence.rb +84 -0
- data/lib/deep_cover/coverage.rb +125 -0
- data/lib/deep_cover/covered_code.rb +145 -0
- data/lib/deep_cover/custom_requirer.rb +187 -0
- data/lib/deep_cover/flag_comment_associator.rb +68 -0
- data/lib/deep_cover/load.rb +66 -0
- data/lib/deep_cover/memoize.rb +48 -0
- data/lib/deep_cover/module_override.rb +39 -0
- data/lib/deep_cover/node/arguments.rb +51 -0
- data/lib/deep_cover/node/assignments.rb +273 -0
- data/lib/deep_cover/node/base.rb +155 -0
- data/lib/deep_cover/node/begin.rb +27 -0
- data/lib/deep_cover/node/block.rb +61 -0
- data/lib/deep_cover/node/branch.rb +32 -0
- data/lib/deep_cover/node/case.rb +113 -0
- data/lib/deep_cover/node/collections.rb +31 -0
- data/lib/deep_cover/node/const.rb +12 -0
- data/lib/deep_cover/node/def.rb +40 -0
- data/lib/deep_cover/node/empty_body.rb +32 -0
- data/lib/deep_cover/node/exceptions.rb +79 -0
- data/lib/deep_cover/node/if.rb +73 -0
- data/lib/deep_cover/node/keywords.rb +86 -0
- data/lib/deep_cover/node/literals.rb +100 -0
- data/lib/deep_cover/node/loops.rb +74 -0
- data/lib/deep_cover/node/mixin/can_augment_children.rb +65 -0
- data/lib/deep_cover/node/mixin/check_completion.rb +18 -0
- data/lib/deep_cover/node/mixin/child_can_be_empty.rb +27 -0
- data/lib/deep_cover/node/mixin/executed_after_children.rb +15 -0
- data/lib/deep_cover/node/mixin/execution_location.rb +66 -0
- data/lib/deep_cover/node/mixin/filters.rb +47 -0
- data/lib/deep_cover/node/mixin/flow_accounting.rb +71 -0
- data/lib/deep_cover/node/mixin/has_child.rb +145 -0
- data/lib/deep_cover/node/mixin/has_child_handler.rb +75 -0
- data/lib/deep_cover/node/mixin/has_tracker.rb +46 -0
- data/lib/deep_cover/node/mixin/is_statement.rb +20 -0
- data/lib/deep_cover/node/mixin/rewriting.rb +35 -0
- data/lib/deep_cover/node/mixin/wrapper.rb +15 -0
- data/lib/deep_cover/node/module.rb +66 -0
- data/lib/deep_cover/node/root.rb +20 -0
- data/lib/deep_cover/node/send.rb +161 -0
- data/lib/deep_cover/node/short_circuit.rb +42 -0
- data/lib/deep_cover/node/splat.rb +15 -0
- data/lib/deep_cover/node/variables.rb +16 -0
- data/lib/deep_cover/node.rb +23 -0
- data/lib/deep_cover/parser_ext/range.rb +21 -0
- data/lib/deep_cover/problem_with_diagnostic.rb +63 -0
- data/lib/deep_cover/reporter/base.rb +68 -0
- data/lib/deep_cover/reporter/html/base.rb +14 -0
- data/lib/deep_cover/reporter/html/index.rb +59 -0
- data/lib/deep_cover/reporter/html/site.rb +68 -0
- data/lib/deep_cover/reporter/html/source.rb +136 -0
- data/lib/deep_cover/reporter/html/template/assets/32px.png +0 -0
- data/lib/deep_cover/reporter/html/template/assets/40px.png +0 -0
- data/lib/deep_cover/reporter/html/template/assets/deep_cover.css +291 -0
- data/lib/deep_cover/reporter/html/template/assets/deep_cover.css.sass +336 -0
- data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.js +4 -0
- data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.map +1 -0
- data/lib/deep_cover/reporter/html/template/assets/jstree.css +1108 -0
- data/lib/deep_cover/reporter/html/template/assets/jstree.js +8424 -0
- data/lib/deep_cover/reporter/html/template/assets/jstreetable.js +1069 -0
- data/lib/deep_cover/reporter/html/template/assets/throbber.gif +0 -0
- data/lib/deep_cover/reporter/html/template/index.html.erb +75 -0
- data/lib/deep_cover/reporter/html/template/source.html.erb +35 -0
- data/lib/deep_cover/reporter/html.rb +15 -0
- data/lib/deep_cover/reporter/istanbul.rb +184 -0
- data/lib/deep_cover/reporter/text.rb +58 -0
- data/lib/deep_cover/reporter/tree/util.rb +86 -0
- data/lib/deep_cover/reporter.rb +10 -0
- data/lib/deep_cover/tools/blank.rb +25 -0
- data/lib/deep_cover/tools/builtin_coverage.rb +55 -0
- data/lib/deep_cover/tools/camelize.rb +13 -0
- data/lib/deep_cover/tools/content_tag.rb +11 -0
- data/lib/deep_cover/tools/covered.rb +9 -0
- data/lib/deep_cover/tools/execute_sample.rb +34 -0
- data/lib/deep_cover/tools/format.rb +18 -0
- data/lib/deep_cover/tools/format_char_cover.rb +19 -0
- data/lib/deep_cover/tools/format_generated_code.rb +27 -0
- data/lib/deep_cover/tools/indent_string.rb +26 -0
- data/lib/deep_cover/tools/merge.rb +16 -0
- data/lib/deep_cover/tools/number_lines.rb +22 -0
- data/lib/deep_cover/tools/our_coverage.rb +11 -0
- data/lib/deep_cover/tools/profiling.rb +68 -0
- data/lib/deep_cover/tools/render_template.rb +13 -0
- data/lib/deep_cover/tools/require_relative_dir.rb +12 -0
- data/lib/deep_cover/tools/scan_match_datas.rb +10 -0
- data/lib/deep_cover/tools/silence_warnings.rb +18 -0
- data/lib/deep_cover/tools/slice.rb +9 -0
- data/lib/deep_cover/tools/strip_heredoc.rb +18 -0
- data/lib/deep_cover/tools/truncate_backtrace.rb +32 -0
- data/lib/deep_cover/tools.rb +22 -0
- data/lib/deep_cover/tracker_bucket.rb +50 -0
- data/lib/deep_cover/tracker_hits_per_path.rb +35 -0
- data/lib/deep_cover/tracker_storage.rb +76 -0
- data/lib/deep_cover/tracker_storage_per_path.rb +34 -0
- data/lib/deep_cover/version.rb +5 -0
- data/lib/deep_cover.rb +22 -0
- metadata +329 -0
Binary file
|
@@ -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,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,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
|