deep-cover 0.1.16 → 0.2.0
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 +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +3 -8
- data/.travis.yml +4 -4
- data/CHANGELOG.md +10 -0
- data/Gemfile +3 -1
- data/README.md +9 -4
- data/Rakefile +6 -3
- data/deep_cover.gemspec +2 -2
- data/lib/deep_cover.rb +10 -0
- data/lib/deep_cover/analyser.rb +1 -2
- data/lib/deep_cover/analyser/base.rb +32 -0
- data/lib/deep_cover/analyser/branch.rb +19 -4
- data/lib/deep_cover/analyser/node.rb +52 -0
- data/lib/deep_cover/analyser/per_char.rb +18 -1
- data/lib/deep_cover/analyser/stats.rb +54 -0
- data/lib/deep_cover/backports.rb +1 -0
- data/lib/deep_cover/base.rb +17 -1
- data/lib/deep_cover/builtin_takeover.rb +5 -0
- data/lib/deep_cover/cli/deep_cover.rb +2 -1
- data/lib/deep_cover/cli/instrumented_clone_reporter.rb +12 -10
- data/lib/deep_cover/config.rb +22 -8
- data/lib/deep_cover/core_ext/coverage_replacement.rb +22 -18
- data/lib/deep_cover/coverage.rb +3 -203
- data/lib/deep_cover/coverage/analysis.rb +36 -0
- data/lib/deep_cover/coverage/base.rb +91 -0
- data/lib/deep_cover/coverage/istanbul.rb +34 -0
- data/lib/deep_cover/coverage/persistence.rb +93 -0
- data/lib/deep_cover/covered_code.rb +12 -22
- data/lib/deep_cover/custom_requirer.rb +6 -2
- data/lib/deep_cover/node/base.rb +1 -1
- data/lib/deep_cover/node/case.rb +13 -2
- data/lib/deep_cover/node/exceptions.rb +2 -2
- data/lib/deep_cover/node/if.rb +21 -2
- data/lib/deep_cover/node/mixin/flow_accounting.rb +1 -0
- data/lib/deep_cover/node/send.rb +9 -2
- data/lib/deep_cover/node/short_circuit.rb +10 -0
- data/lib/deep_cover/parser_ext/range.rb +4 -4
- data/lib/deep_cover/reporter/html.rb +15 -0
- data/lib/deep_cover/reporter/html/base.rb +14 -0
- data/lib/deep_cover/reporter/html/index.rb +78 -0
- data/lib/deep_cover/reporter/html/site.rb +78 -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.sass +338 -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/tree.rb +55 -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/merge.rb +16 -0
- data/lib/deep_cover/tools/render_template.rb +13 -0
- data/lib/deep_cover/tools/transform_keys.rb +9 -0
- data/lib/deep_cover/version.rb +1 -1
- metadata +33 -7
- data/lib/deep_cover/analyser/ignore_uncovered.rb +0 -21
- data/lib/deep_cover/analyser/optionally_covered.rb +0 -19
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Coverage::Istanbul
|
5
|
+
def to_istanbul(**options)
|
6
|
+
map do |covered_code|
|
7
|
+
next {} unless covered_code.has_executed?
|
8
|
+
covered_code.to_istanbul(**options)
|
9
|
+
end.inject(:merge)
|
10
|
+
end
|
11
|
+
|
12
|
+
def output_istanbul(dir: '.', name: '.nyc_output', **options)
|
13
|
+
path = Pathname.new(dir).expand_path.join(name)
|
14
|
+
path.mkpath
|
15
|
+
path.each_child(&:delete)
|
16
|
+
path.join('deep_cover.json').write(JSON.pretty_generate(to_istanbul(**options)))
|
17
|
+
path
|
18
|
+
end
|
19
|
+
|
20
|
+
def report_istanbul(output: nil, **options)
|
21
|
+
dir = output_istanbul(**options).dirname
|
22
|
+
unless [nil, '', 'false'].include? output
|
23
|
+
output = File.expand_path(output)
|
24
|
+
html = "--reporter=html --report-dir='#{output}'"
|
25
|
+
if options[:open]
|
26
|
+
html += " && open '#{output}/index.html'"
|
27
|
+
else
|
28
|
+
msg = "\nHTML coverage written to: '#{output}/index.html'"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
`cd #{dir} && nyc report --reporter=text #{html}` + msg.to_s
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
require 'securerandom'
|
5
|
+
class Coverage::Persistence
|
6
|
+
# rubocop:disable Security/MarshalLoad
|
7
|
+
BASENAME = 'coverage.dc'
|
8
|
+
TRACKER_TEMPLATE = 'trackers%{unique}.dct'
|
9
|
+
|
10
|
+
attr_reader :dir_path
|
11
|
+
def initialize(dest_path, dirname)
|
12
|
+
@dir_path = Pathname(dest_path).join(dirname).expand_path
|
13
|
+
end
|
14
|
+
|
15
|
+
def load(with_trackers: true)
|
16
|
+
saved?
|
17
|
+
load_trackers if with_trackers
|
18
|
+
load_coverage
|
19
|
+
end
|
20
|
+
|
21
|
+
def save(coverage)
|
22
|
+
create_if_needed
|
23
|
+
delete_trackers
|
24
|
+
save_coverage(coverage)
|
25
|
+
end
|
26
|
+
|
27
|
+
def save_trackers(global)
|
28
|
+
saved?
|
29
|
+
trackers = eval(global) # rubocop:disable Security/Eval
|
30
|
+
# Some testing involves more than one process, some of which don't run any of our covered code.
|
31
|
+
# Don't save anything if that's the case
|
32
|
+
return if trackers.nil?
|
33
|
+
basename = format(TRACKER_TEMPLATE, unique: SecureRandom.urlsafe_base64)
|
34
|
+
dir_path.join(basename).binwrite(Marshal.dump(
|
35
|
+
version: DeepCover::VERSION,
|
36
|
+
global: global,
|
37
|
+
trackers: trackers,
|
38
|
+
))
|
39
|
+
end
|
40
|
+
|
41
|
+
def saved?
|
42
|
+
raise "Can't find folder '#{dir_path}'" unless dir_path.exist?
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def create_if_needed
|
49
|
+
dir_path.mkpath
|
50
|
+
end
|
51
|
+
|
52
|
+
def save_coverage(coverage)
|
53
|
+
dir_path.join(BASENAME).binwrite(Marshal.dump(
|
54
|
+
version: DeepCover::VERSION,
|
55
|
+
coverage: coverage,
|
56
|
+
))
|
57
|
+
end
|
58
|
+
|
59
|
+
def load_coverage
|
60
|
+
Marshal.load(dir_path.join(BASENAME).binread).tap do |version: raise, coverage: raise|
|
61
|
+
raise "dump version mismatch: #{version}, currently #{DeepCover::VERSION}" unless version == DeepCover::VERSION
|
62
|
+
return coverage
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def load_trackers
|
67
|
+
tracker_files.each do |full_path|
|
68
|
+
Marshal.load(full_path.binread).tap do |version: raise, global: raise, trackers: raise|
|
69
|
+
raise "dump version mismatch: #{version}, currently #{DeepCover::VERSION}" unless version == DeepCover::VERSION
|
70
|
+
merge_trackers(eval("#{global} ||= {}"), trackers) # rubocop:disable Security/Eval
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def merge_trackers(hash, to_merge)
|
76
|
+
hash.merge!(to_merge) do |_key, current, to_add|
|
77
|
+
unless current.empty? || current.size == to_add.size
|
78
|
+
warn "Merging trackers of different sizes: #{current.size} vs #{to_add.size}"
|
79
|
+
end
|
80
|
+
to_add.zip(current).map { |a, b| a + b }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def tracker_files
|
85
|
+
basename = format(TRACKER_TEMPLATE, unique: '*')
|
86
|
+
Pathname.glob(dir_path.join(basename))
|
87
|
+
end
|
88
|
+
|
89
|
+
def delete_trackers
|
90
|
+
tracker_files.each(&:delete)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -12,11 +12,11 @@ module DeepCover
|
|
12
12
|
raise 'Must provide either path or source' unless path || source
|
13
13
|
|
14
14
|
@buffer = ::Parser::Source::Buffer.new(path, lineno)
|
15
|
-
@buffer.source = source
|
15
|
+
@buffer.source = source || File.read(path)
|
16
16
|
@tracker_count = 0
|
17
17
|
@tracker_global = tracker_global
|
18
18
|
@local_var = local_var
|
19
|
-
@name = name || (
|
19
|
+
@name = name.to_s || (path ? File.basename(path) : '(source)')
|
20
20
|
@covered_source = instrument_source
|
21
21
|
end
|
22
22
|
|
@@ -39,28 +39,23 @@ module DeepCover
|
|
39
39
|
|
40
40
|
def execute_code(binding: DeepCover::GLOBAL_BINDING.dup)
|
41
41
|
return if has_executed?
|
42
|
-
global[nb] = Array.new(@tracker_count, 0)
|
43
42
|
eval(@covered_source, binding, @buffer.name || '<raw_code>', lineno) # rubocop:disable Security/Eval
|
44
43
|
self
|
45
44
|
end
|
46
45
|
|
47
46
|
def cover
|
48
|
-
|
49
|
-
global[nb]
|
47
|
+
global[nb] ||= Array.new(@tracker_count, 0)
|
50
48
|
end
|
51
49
|
|
52
50
|
def line_coverage(**options)
|
53
|
-
must_have_executed
|
54
51
|
Analyser::PerLine.new(self, **options).results
|
55
52
|
end
|
56
53
|
|
57
54
|
def to_istanbul(**options)
|
58
|
-
must_have_executed
|
59
55
|
Reporter::Istanbul.new(self, **options).convert
|
60
56
|
end
|
61
57
|
|
62
58
|
def char_cover(**options)
|
63
|
-
must_have_executed
|
64
59
|
Analyser::PerChar.new(self, **options).results
|
65
60
|
end
|
66
61
|
|
@@ -103,18 +98,13 @@ module DeepCover
|
|
103
98
|
end
|
104
99
|
|
105
100
|
def instrument_source
|
106
|
-
rewriter = ::Parser::Source::
|
101
|
+
rewriter = ::Parser::Source::TreeRewriter.new(@buffer)
|
107
102
|
covered_ast.each_node(:postorder) do |node|
|
108
103
|
node.rewriting_rules.each do |range, rule|
|
109
104
|
prefix, _node, suffix = rule.partition('%{node}')
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
114
|
-
unless suffix.empty?
|
115
|
-
suffix = yield suffix, node, range.end, :suffix if block_given?
|
116
|
-
rewriter.insert_after_multi range, suffix
|
117
|
-
end
|
105
|
+
prefix = yield prefix, node, range.begin, :prefix if block_given? && !prefix.empty?
|
106
|
+
suffix = yield suffix, node, range.end, :suffix if block_given? && !suffix.empty?
|
107
|
+
rewriter.wrap(range, prefix, suffix)
|
118
108
|
end
|
119
109
|
end
|
120
110
|
rewriter.process
|
@@ -126,21 +116,21 @@ module DeepCover
|
|
126
116
|
|
127
117
|
def freeze
|
128
118
|
unless frozen? # Guard against reentrance
|
129
|
-
must_have_executed
|
130
119
|
super
|
131
120
|
root.each_node(&:freeze)
|
132
121
|
end
|
133
122
|
self
|
134
123
|
end
|
135
124
|
|
125
|
+
def inspect
|
126
|
+
%{#<DeepCover::CoveredCode "#{name}">}
|
127
|
+
end
|
128
|
+
alias_method :to_s, :inspect
|
129
|
+
|
136
130
|
protected
|
137
131
|
|
138
132
|
def global
|
139
133
|
@@globals[tracker_global]
|
140
134
|
end
|
141
|
-
|
142
|
-
def must_have_executed
|
143
|
-
raise "cover for #{buffer.name} not available, file wasn't executed" unless has_executed?
|
144
|
-
end
|
145
135
|
end
|
146
136
|
end
|
@@ -5,6 +5,8 @@
|
|
5
5
|
module DeepCover
|
6
6
|
class CustomRequirer
|
7
7
|
class LoadPathsSubset
|
8
|
+
attr_reader :last_lookup_path
|
9
|
+
|
8
10
|
def initialize(load_paths: raise, lookup_paths: raise)
|
9
11
|
@original_load_paths = load_paths
|
10
12
|
@cached_load_paths_subset = []
|
@@ -27,7 +29,7 @@ module DeepCover
|
|
27
29
|
|
28
30
|
# E.g. '/a/b' => true when a lookup path is '/a/'
|
29
31
|
def within_lookup?(full_path)
|
30
|
-
@lookup_paths.any? { |p| full_path.start_with? p }
|
32
|
+
@lookup_paths.any? { |p| full_path.start_with?(p) && @last_lookup_path = p }
|
31
33
|
end
|
32
34
|
|
33
35
|
def exist?(full_path)
|
@@ -86,6 +88,7 @@ module DeepCover
|
|
86
88
|
# It is *NOT* recommended to simply delegate to the default #require, since it
|
87
89
|
# might not be safe to run part of the code again.
|
88
90
|
def require(path)
|
91
|
+
path = path.to_s
|
89
92
|
ext = File.extname(path)
|
90
93
|
throw :use_fallback, :not_supported if ext == '.so'
|
91
94
|
path += '.rb' if ext != '.rb'
|
@@ -128,7 +131,8 @@ module DeepCover
|
|
128
131
|
|
129
132
|
def cover_and_execute(path)
|
130
133
|
begin
|
131
|
-
|
134
|
+
name = path.sub(%r{^#{@load_paths_subset.last_lookup_path}/}, '') if @load_paths_subset
|
135
|
+
covered_code = DeepCover.coverage.covered_code(path, name: name)
|
132
136
|
rescue Parser::SyntaxError => e
|
133
137
|
if e.message =~ /contains escape sequences incompatible with UTF-8/
|
134
138
|
warn "Can't cover #{path} because of incompatible encoding (see issue #9)"
|
data/lib/deep_cover/node/base.rb
CHANGED
data/lib/deep_cover/node/case.rb
CHANGED
@@ -51,10 +51,10 @@ module DeepCover
|
|
51
51
|
if (after_then = base_node.loc.begin)
|
52
52
|
after_then.end
|
53
53
|
else
|
54
|
-
base_node.loc.expression.end
|
54
|
+
base_node.loc.expression.end.succ
|
55
55
|
end
|
56
56
|
},
|
57
|
-
rewrite: '
|
57
|
+
rewrite: '%{body_entry_tracker};%{local}=nil;%{node}',
|
58
58
|
is_statement: true,
|
59
59
|
flow_entry_count: :body_entry_tracker_hits
|
60
60
|
|
@@ -89,6 +89,17 @@ module DeepCover
|
|
89
89
|
whens.map(&:body) << self.else
|
90
90
|
end
|
91
91
|
|
92
|
+
def branches_summary(of = branches)
|
93
|
+
texts = []
|
94
|
+
n = of.size
|
95
|
+
if of.include? self.else
|
96
|
+
texts << "#{'implicit ' unless has_else?}else"
|
97
|
+
n -= 1
|
98
|
+
end
|
99
|
+
texts.unshift "#{n} when clause#{'s' if n > 1}" if n > 0
|
100
|
+
texts.join(' and ')
|
101
|
+
end
|
102
|
+
|
92
103
|
def execution_count
|
93
104
|
return evaluate.flow_completion_count if evaluate
|
94
105
|
flow_entry_count
|
@@ -10,7 +10,7 @@ module DeepCover
|
|
10
10
|
has_child exception: [Node::Array, nil]
|
11
11
|
has_child assignment: [Lvasgn, nil], flow_entry_count: :entered_body_tracker_hits
|
12
12
|
has_child body: Node,
|
13
|
-
can_be_empty: -> { base_node.loc.expression.end },
|
13
|
+
can_be_empty: -> { (base_node.loc.begin || base_node.loc.expression.succ).end },
|
14
14
|
flow_entry_count: :entered_body_tracker_hits,
|
15
15
|
is_statement: true,
|
16
16
|
rewrite: '((%{entered_body_tracker};%{local}=nil;%{node}))'
|
@@ -67,7 +67,7 @@ module DeepCover
|
|
67
67
|
can_be_empty: -> { base_node.loc.expression.begin },
|
68
68
|
is_statement: true
|
69
69
|
has_child ensure: Node,
|
70
|
-
can_be_empty: -> { base_node.loc.expression.end },
|
70
|
+
can_be_empty: -> { base_node.loc.expression.end.succ },
|
71
71
|
is_statement: true,
|
72
72
|
flow_entry_count: -> { body.flow_entry_count }
|
73
73
|
|
data/lib/deep_cover/node/if.rb
CHANGED
@@ -9,21 +9,36 @@ module DeepCover
|
|
9
9
|
has_tracker :truthy
|
10
10
|
has_child condition: Node, rewrite: '((%{node}) && %{truthy_tracker})'
|
11
11
|
has_child true_branch: Node,
|
12
|
-
can_be_empty: true,
|
13
12
|
executed_loc_keys: -> { :else if style == :unless },
|
14
13
|
flow_entry_count: :truthy_tracker_hits,
|
15
14
|
is_statement: true
|
16
15
|
has_child false_branch: Node,
|
17
|
-
can_be_empty: true,
|
18
16
|
executed_loc_keys: -> { [:else, :colon] if style != :unless },
|
19
17
|
flow_entry_count: -> { condition.flow_completion_count - truthy_tracker_hits },
|
20
18
|
is_statement: true
|
21
19
|
executed_loc_keys :keyword, :question
|
22
20
|
|
21
|
+
def child_can_be_empty(child, name)
|
22
|
+
return false if name == :condition || style == :ternary
|
23
|
+
if (name == :true_branch) == (style == :if)
|
24
|
+
(base_node.loc.begin || base_node.children[0].loc.expression.succ).end
|
25
|
+
elsif has_else?
|
26
|
+
base_node.loc.else.end.succ
|
27
|
+
else
|
28
|
+
true # implicit else
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
23
32
|
def branches
|
24
33
|
[true_branch, false_branch]
|
25
34
|
end
|
26
35
|
|
36
|
+
def branches_summary(of = branches)
|
37
|
+
of.map do |jump|
|
38
|
+
"#{'implicit ' if jump.is_a?(EmptyBody) && !has_else?}#{jump == false_branch ? 'falsy' : 'truthy'} branch"
|
39
|
+
end.join(' and ')
|
40
|
+
end
|
41
|
+
|
27
42
|
def execution_count
|
28
43
|
condition.flow_completion_count
|
29
44
|
end
|
@@ -33,6 +48,10 @@ module DeepCover
|
|
33
48
|
keyword = loc_hash[:keyword]
|
34
49
|
keyword ? keyword.source.to_sym : :ternary
|
35
50
|
end
|
51
|
+
|
52
|
+
def has_else?
|
53
|
+
!!base_node.loc.to_hash[:else]
|
54
|
+
end
|
36
55
|
end
|
37
56
|
end
|
38
57
|
end
|
@@ -36,6 +36,7 @@ module DeepCover
|
|
36
36
|
end
|
37
37
|
|
38
38
|
# Returns number of times the node itself was "executed". Definition of executed depends on the node.
|
39
|
+
# For now at least, don't return `nil`, instead return `false` in `executable?`
|
39
40
|
def execution_count
|
40
41
|
flow_entry_count
|
41
42
|
end
|
data/lib/deep_cover/node/send.rb
CHANGED
@@ -58,6 +58,7 @@ module DeepCover
|
|
58
58
|
arguments.empty? || # No issue when no arguments
|
59
59
|
loc_hash[:selector_end] || # No issue with foo[bar]= and such
|
60
60
|
loc_hash[:operator] || # No issue with foo.bar=
|
61
|
+
(receiver && !loc_hash[:dot]) || # No issue with foo + bar
|
61
62
|
loc_hash[:begin] # Ok if has parentheses
|
62
63
|
end
|
63
64
|
end
|
@@ -92,10 +93,16 @@ module DeepCover
|
|
92
93
|
end
|
93
94
|
|
94
95
|
def branches
|
95
|
-
[actual_send,
|
96
|
-
|
96
|
+
[TrivialBranch.new(condition: receiver, other_branch: actual_send),
|
97
|
+
actual_send,
|
97
98
|
]
|
98
99
|
end
|
100
|
+
|
101
|
+
def branches_summary(of = branches)
|
102
|
+
of.map do |jump|
|
103
|
+
jump == actual_send ? 'safe send' : 'nil shortcut'
|
104
|
+
end.join(' and ')
|
105
|
+
end
|
99
106
|
end
|
100
107
|
|
101
108
|
class MatchWithLvasgn < Node
|
@@ -17,6 +17,16 @@ module DeepCover
|
|
17
17
|
TrivialBranch.new(condition: lhs, other_branch: conditional),
|
18
18
|
]
|
19
19
|
end
|
20
|
+
|
21
|
+
def branches_summary(of = branches)
|
22
|
+
of.map do |jump|
|
23
|
+
if jump == conditional
|
24
|
+
'left-hand side'
|
25
|
+
else
|
26
|
+
"#{type == :and ? 'falsy' : 'truthy'} shortcut"
|
27
|
+
end
|
28
|
+
end.join(' and ')
|
29
|
+
end
|
20
30
|
end
|
21
31
|
|
22
32
|
And = Or = ShortCircuit
|
@@ -1,10 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Parser::Source::Range
|
4
|
-
def with(begin_pos: @begin_pos, end_pos: @end_pos)
|
5
|
-
Parser::Source::Range.new(@source_buffer, begin_pos, end_pos)
|
6
|
-
end
|
7
|
-
|
8
4
|
# (1...10).split(2...3, 6...8) => [1...2, 3...6, 7...10]
|
9
5
|
# Assumes inner_ranges are exclusive, and included in self
|
10
6
|
def split(*inner_ranges)
|
@@ -34,4 +30,8 @@ class Parser::Source::Range
|
|
34
30
|
def strip(pattern = /\s*/)
|
35
31
|
lstrip(pattern).rstrip(pattern)
|
36
32
|
end
|
33
|
+
|
34
|
+
def succ
|
35
|
+
adjust(begin_pos: +1, end_pos: +1)
|
36
|
+
end
|
37
37
|
end
|
@@ -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.covered_codes, **options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|