getcov 0.3.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.
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+ require 'fileutils'
3
+ require 'erb'
4
+ require 'cgi'
5
+
6
+ module Getcov
7
+ module Formatter
8
+ class HTML
9
+ INDEX_TEMPLATE = <<~'HTML'
10
+ <!doctype html>
11
+ <html>
12
+ <head>
13
+ <meta charset="utf-8">
14
+ <title>Getcov Coverage</title>
15
+ <style>
16
+ #{CSS}
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <h1>Getcov Coverage</h1>
21
+ <div class="total">
22
+ Total Coverage: <strong><%= format('%.2f%%', result.total_coverage) %></strong>
23
+ <% if result.branch_total_percent %>
24
+ &nbsp;•&nbsp; Branch: <strong><%= format('%.2f%%', result.branch_total_percent) %></strong>
25
+ <% end %>
26
+ </div>
27
+
28
+ <% grouped = result.files.group_by(&:group) %>
29
+ <% grouped.keys.sort_by { |g| g.to_s }.each do |group_name| %>
30
+ <div class="group">
31
+ <h2><%= group_name || 'Ungrouped' %></h2>
32
+ <table class="index-table">
33
+ <thead>
34
+ <tr>
35
+ <th>File</th>
36
+ <th class="pct">Coverage</th>
37
+ <th class="pct">Branch</th>
38
+ <th class="pct">Hits</th>
39
+ </tr>
40
+ </thead>
41
+ <tbody>
42
+ <% grouped[group_name].each do |fr| %>
43
+ <tr>
44
+ <td><a href="<%= file_href(fr) %>"><%= fr.path %></a></td>
45
+ <td class="pct">
46
+ <div class="bar"><span style="width:<%= fr.percent %>%"></span></div>
47
+ <div><%= format('%.2f%%', fr.percent) %></div>
48
+ </td>
49
+ <td class="pct"><%= fr.branch_total ? format('%.2f%%', fr.branch_percent) : '-' %></td>
50
+ <td class="pct"><%= fr.covered %>/<%= fr.relevant %></td>
51
+ </tr>
52
+ <% end %>
53
+ </tbody>
54
+ </table>
55
+ </div>
56
+ <% end %>
57
+ </body>
58
+ </html>
59
+ HTML
60
+
61
+ FILE_TEMPLATE = <<~'HTML'
62
+ <!doctype html>
63
+ <html>
64
+ <head>
65
+ <meta charset="utf-8">
66
+ <title><%= fr.path %> - Getcov Coverage</title>
67
+ <style>
68
+ #{CSS}
69
+ </style>
70
+ </head>
71
+ <body>
72
+ <div class="breadcrumb"><a href="index.html">« Back to index</a></div>
73
+ <h1><%= fr.path %></h1>
74
+ <% if @config.repo_url %>
75
+ <div class="muted">
76
+ <a href="<%= File.join(@config.repo_url, 'blob', @config.repo_branch, fr.path) %>#L1" target="_blank">View on repo</a>
77
+ </div>
78
+ <% end %>
79
+ <div class="total">
80
+ File Coverage: <strong><%= format('%.2f%%', fr.percent) %></strong>
81
+ &nbsp;(<%= fr.covered %>/<%= fr.relevant %> relevant)
82
+ <% if fr.branch_total && fr.branch_total > 0 %>
83
+ &nbsp;•&nbsp; Branch: <strong><%= format('%.2f%%', fr.branch_percent) %></strong>
84
+ &nbsp;(<%= fr.branch_covered %>/<%= fr.branch_total %>)
85
+ <% end %>
86
+ </div>
87
+
88
+ <table class="code-table">
89
+ <thead>
90
+ <tr>
91
+ <th class="ln">#</th>
92
+ <th class="hit">Hit</th>
93
+ <th>Source</th>
94
+ </tr>
95
+ </thead>
96
+ <tbody>
97
+ <% lines = source_lines(fr) %>
98
+ <% lines.each_with_index do |src, idx| %>
99
+ <% hit = fr.hits && fr.hits[idx] %>
100
+ <% cls = hit.nil? ? 'irrelevant' : (hit.to_i > 0 ? 'covered' : 'missed') %>
101
+ <tr class="<%= cls %>">
102
+ <td class="ln"><a id="L<%= idx+1 %>" href="#L<%= idx+1 %>"><%= idx+1 %></a></td>
103
+ <td class="hit"><%= hit.nil? ? '-' : hit.to_i %></td>
104
+ <td class="src"><pre><code><%= CGIFilter.escape(src) %></code></pre></td>
105
+ </tr>
106
+ <% end %>
107
+ </tbody>
108
+ </table>
109
+ </body>
110
+ </html>
111
+ HTML
112
+
113
+ CSS = <<~'CSS'
114
+ body { font-family: -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; margin: 2rem; }
115
+ h1 { margin-top: 0; }
116
+ .total { font-size: 1.1rem; margin-bottom: 1rem; }
117
+ .group { margin-top: 1.5rem; }
118
+ .index-table { border-collapse: collapse; width: 100%; }
119
+ .index-table th, .index-table td { padding: 8px 10px; border-bottom: 1px solid #ddd; text-align: left; }
120
+ .pct { text-align: right; white-space: nowrap; }
121
+ .bar { background: #eee; height: 10px; position: relative; border-radius: 4px; overflow: hidden; }
122
+ .bar > span { position: absolute; top: 0; left: 0; bottom: 0; background: #4caf50; }
123
+ .code-table { border-collapse: collapse; width: 100%; table-layout: fixed; }
124
+ .code-table th, .code-table td { border-bottom: 1px solid #eee; padding: 2px 6px; vertical-align: top; }
125
+ .code-table .ln { width: 4em; color: #777; }
126
+ .code-table .hit { width: 4em; text-align: right; color: #555; }
127
+ .code-table .src pre { margin: 0; white-space: pre-wrap; word-wrap: break-word; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
128
+ tr.covered { background: #f6fff6; }
129
+ tr.missed { background: #fff6f6; }
130
+ tr.irrelevant { background: #fafafa; color: #777; }
131
+ .breadcrumb { margin-bottom: 0.5rem; }
132
+ a { color: #0a58ca; text-decoration: none; }
133
+ a:hover { text-decoration: underline; }
134
+ CSS
135
+
136
+ module CGIFilter
137
+ module_function
138
+ def escape(s) CGI.escapeHTML(s.to_s) end
139
+ end
140
+
141
+ def initialize(config) @config = config end
142
+
143
+ def write(result)
144
+ @result = result
145
+ out_dir = @config.output_dir
146
+ FileUtils.mkdir_p(out_dir)
147
+
148
+ File.write(File.join(out_dir, 'index.html'), ERB.new(INDEX_TEMPLATE).result(binding))
149
+ result.files.each do |fr|
150
+ html = ERB.new(FILE_TEMPLATE).result(binding)
151
+ File.write(file_path(fr), html)
152
+ end
153
+ end
154
+
155
+ private
156
+
157
+ def file_href(fr) fr.path.gsub(File::SEPARATOR, '__') + '.html' end
158
+ def file_path(fr) File.join(@config.output_dir, file_href(fr)) end
159
+
160
+ def source_lines(fr)
161
+ abs = File.expand_path(fr.path, @config.root)
162
+ if File.exist?(abs)
163
+ File.read(abs).split(/\r?\n/, -1)
164
+ else
165
+ Array.new(fr.hits ? fr.hits.length : 0, '')
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ require 'fileutils'
3
+
4
+ module Getcov
5
+ module Formatter
6
+ class LCOV
7
+ def initialize(config) @config = config end
8
+
9
+ def write(result)
10
+ out_dir = @config.output_dir
11
+ FileUtils.mkdir_p(out_dir)
12
+ File.open(File.join(out_dir, 'lcov.info'), 'w') do |f|
13
+ result.files.each do |fr|
14
+ f.puts "TN:"
15
+ f.puts "SF:#{fr.path}"
16
+ total = 0
17
+ covered = 0
18
+ fr.hits&.each_with_index do |hit, idx|
19
+ next if hit.nil?
20
+ line_no = idx + 1
21
+ f.puts "DA:#{line_no},#{hit.to_i}"
22
+ total += 1
23
+ covered += 1 if hit.to_i > 0
24
+ end
25
+ f.puts "LH:#{covered}"
26
+ f.puts "LF:#{total}"
27
+ f.puts "end_of_record"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ require 'fileutils'
3
+
4
+ module Getcov
5
+ module Formatter
6
+ class PRComment
7
+ def initialize(config) @config = config end
8
+
9
+ def write(result)
10
+ out_dir = @config.output_dir
11
+ FileUtils.mkdir_p(out_dir)
12
+ md = build_markdown(result)
13
+ File.write(File.join(out_dir, 'pr_comment.md'), md)
14
+ # GitHub Actions step summary support if available
15
+ if ENV['GITHUB_STEP_SUMMARY'] && !ENV['GITHUB_STEP_SUMMARY'].empty?
16
+ File.open(ENV['GITHUB_STEP_SUMMARY'], 'a') { |f| f.puts(md) }
17
+ end
18
+ end
19
+
20
+ def build_markdown(result)
21
+ lines = []
22
+ lines << "## Getcov Coverage\n"
23
+ lines << "**Total**: #{format('%.2f%%', result.total_coverage)}"
24
+ if result.branch_total_percent
25
+ lines << " • **Branches**: #{format('%.2f%%', result.branch_total_percent)}"
26
+ end
27
+ lines << "\n"
28
+ lines << "### Groups\n"
29
+ gc = result.group_coverage
30
+ gc.keys.sort.each do |g|
31
+ lines << "- **#{g}**: #{format('%.2f%%', gc[g])}"
32
+ end
33
+ lines << "\n### Files (top misses)\n"
34
+ misses = result.files.sort_by { |fr| fr.percent }.take(10)
35
+ misses.each do |fr|
36
+ lines << "- `#{fr.path}` — #{format('%.2f%%', fr.percent)} (#{fr.covered}/#{fr.relevant})"
37
+ end
38
+ lines << "\n_Report generated by getcov_.\n"
39
+ lines.join("\n")
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ require 'fileutils'
3
+ require 'json'
4
+
5
+ module Getcov
6
+ module Formatter
7
+ class SummaryJSON
8
+ def initialize(config) @config = config end
9
+
10
+ def write(result)
11
+ out_dir = @config.output_dir
12
+ FileUtils.mkdir_p(out_dir)
13
+ data = {
14
+ total: result.total_coverage,
15
+ branch_total: result.branch_total_percent,
16
+ groups: result.group_coverage,
17
+ files: result.files.map { |fr|
18
+ {
19
+ path: fr.path,
20
+ percent: fr.percent,
21
+ covered: fr.covered,
22
+ relevant: fr.relevant,
23
+ branch_percent: fr.branch_percent,
24
+ branch_covered: fr.branch_covered,
25
+ branch_total: fr.branch_total
26
+ }
27
+ }
28
+ }
29
+ File.write(File.join(out_dir, 'summary.json'), JSON.pretty_generate(data))
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+ require 'pathname'
3
+
4
+ module Getcov
5
+ class Result
6
+ FileReport = Struct.new(:path, :covered, :relevant, :percent,
7
+ :group, :hits,
8
+ :branch_covered, :branch_total, :branch_percent,
9
+ keyword_init: true)
10
+
11
+ attr_reader :files, :total_coverage, :branch_total_percent
12
+
13
+ def initialize(raw_result, config)
14
+ @config = config
15
+ @files = []
16
+
17
+ tracked = config.tracked_paths
18
+ tracked.each { |t| raw_result[t] ||= nil }
19
+
20
+ raw_result.each do |path, lines_or_hash|
21
+ next unless path && File.extname(path) == '.rb'
22
+ rel = relative(path)
23
+ next if @config.filtered?(rel)
24
+
25
+ lines, branches = normalize(lines_or_hash)
26
+
27
+ hits = (lines || [])
28
+ relevant = hits.count { |h| !h.nil? }
29
+ covered = hits.count { |h| h && h > 0 }
30
+ percent = relevant.zero? ? 100.0 : (covered.to_f / relevant * 100.0)
31
+
32
+ b_cov = b_tot = b_pct = nil
33
+ if branches && branches['total'].to_i > 0
34
+ b_cov = branches['covered'].to_i
35
+ b_tot = branches['total'].to_i
36
+ b_pct = (b_cov.to_f / b_tot * 100.0)
37
+ end
38
+
39
+ group = @config.group_for(rel)
40
+
41
+ @files << FileReport.new(path: rel, covered: covered, relevant: relevant,
42
+ percent: percent, group: group, hits: hits,
43
+ branch_covered: b_cov, branch_total: b_tot, branch_percent: b_pct)
44
+ end
45
+
46
+ total_rel = @files.sum(&:relevant)
47
+ total_cov = @files.sum(&:covered)
48
+ @total_coverage = total_rel.zero? ? 100.0 : (total_cov.to_f / total_rel * 100.0)
49
+
50
+ tot_bcov = @files.sum { |fr| fr.branch_covered.to_i }
51
+ tot_btot = @files.sum { |fr| fr.branch_total.to_i }
52
+ @branch_total_percent = (tot_btot.zero? ? nil : (tot_bcov.to_f / tot_btot * 100.0))
53
+
54
+ @files.sort_by! { |fr| [fr.group.to_s, fr.path] }
55
+ end
56
+
57
+ def print_summary(io = $stdout)
58
+ io.puts '== Getcov Coverage Summary =='
59
+ io.puts format('Total Coverage: %.2f%%', total_coverage)
60
+ if @branch_total_percent
61
+ io.puts format('Total Branch Coverage: %.2f%%', @branch_total_percent)
62
+ end
63
+ unless @files.empty?
64
+ io.puts ''
65
+ current_group = nil
66
+ @files.each do |fr|
67
+ if fr.group != current_group
68
+ io.puts '' unless current_group.nil?
69
+ io.puts "[#{fr.group || 'Ungrouped'}]"
70
+ current_group = fr.group
71
+ end
72
+ line = format(' %-60s %6.2f%% (%d/%d)', fr.path, fr.percent, fr.covered, fr.relevant)
73
+ if fr.branch_total && fr.branch_total > 0
74
+ line += format(' | Branch: %6.2f%% (%d/%d)', fr.branch_percent, fr.branch_covered, fr.branch_total)
75
+ end
76
+ io.puts line
77
+ end
78
+ end
79
+ io
80
+ end
81
+
82
+ def group_coverage
83
+ groups = Hash.new { |h,k| h[k] = { cov: 0, rel: 0 } }
84
+ @files.each do |fr|
85
+ g = fr.group || 'Ungrouped'
86
+ groups[g][:cov] += fr.covered
87
+ groups[g][:rel] += fr.relevant
88
+ end
89
+ groups.transform_values { |v| v[:rel].zero? ? 100.0 : (v[:cov].to_f / v[:rel] * 100.0) }
90
+ end
91
+
92
+ private
93
+
94
+ def relative(path)
95
+ Pathname(path).relative_path_from(Pathname(@config.root)).to_s rescue path
96
+ end
97
+
98
+ def normalize(v)
99
+ if v.is_a?(Array)
100
+ [v, nil]
101
+ elsif v.is_a?(Hash)
102
+ lines = v['lines'] || v[:lines]
103
+ branches = v['branches'] || v[:branches]
104
+ if branches && !branches.is_a?(Hash) && branches.respond_to?(:to_h)
105
+ branches = branches.to_h
106
+ end
107
+ branches = nil unless branches.nil? || branches.is_a?(Hash)
108
+ [lines, branches]
109
+ else
110
+ [nil, nil]
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Getcov
3
+ VERSION = '0.3.0'
4
+ end
data/lib/getcov.rb ADDED
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'coverage'
4
+ require 'json'
5
+ require 'fileutils'
6
+ require 'getcov/configuration'
7
+ require 'getcov/result'
8
+
9
+ module Getcov
10
+ class << self
11
+ def configuration
12
+ @configuration ||= Configuration.new
13
+ end
14
+
15
+ def configure
16
+ yield(configuration)
17
+ end
18
+
19
+ def start
20
+ return if @started
21
+ begin
22
+ Coverage.start(lines: true, branches: true)
23
+ rescue ArgumentError
24
+ Coverage.start(lines: true)
25
+ end
26
+ @started = true
27
+ end
28
+
29
+ def started? = !!@started
30
+
31
+ def stop
32
+ Coverage.result
33
+ end
34
+
35
+ # Merge multiple raw results (array of Hash[path => hits or { 'lines'=>[], 'branches'=>... }])
36
+ def merge_raw_results(raws)
37
+ out = {}
38
+ raws.each do |raw|
39
+ raw.each do |path, v|
40
+ out[path] ||= nil
41
+ out[path] = merge_value(out[path], v)
42
+ end
43
+ end
44
+ out
45
+ end
46
+
47
+ def write_partial!(raw)
48
+ dir = configuration.parallel_merge_dir
49
+ raise 'parallel_merge_dir not configured' unless dir
50
+ FileUtils.mkdir_p(dir)
51
+ File.write(File.join(dir, "getcov-#{Process.pid}.json"), JSON.pretty_generate(raw))
52
+ end
53
+
54
+ def load_and_merge_partials
55
+ dir = configuration.parallel_merge_dir
56
+ raise 'parallel_merge_dir not configured' unless dir
57
+ raws = Dir[File.join(dir, '*.json')].map { |f| JSON.parse(File.read(f)) }
58
+ merge_raw_results(raws)
59
+ end
60
+
61
+ def finalize!
62
+ raw = stop
63
+
64
+ if configuration.parallel_merge_dir
65
+ if ENV['GETCOV_ROLE'] == 'worker'
66
+ write_partial!(raw)
67
+ return
68
+ elsif ENV['GETCOV_ROLE'] == 'master'
69
+ parts = []
70
+ parts << raw if raw && !raw.empty?
71
+ parts << load_and_merge_partials
72
+ raw = merge_raw_results(parts)
73
+ end
74
+ end
75
+
76
+ result = Result.new(raw, configuration)
77
+ result.print_summary
78
+
79
+ configuration.formatters.each do |fmt|
80
+ formatter = fmt.respond_to?(:new) ? fmt.new(configuration) : fmt
81
+ formatter.write(result)
82
+ end
83
+
84
+ failure = false
85
+ per_file_failures = []
86
+ if configuration.minimum_coverage && result.total_coverage < configuration.minimum_coverage
87
+ warn format('Coverage %.2f%% is below the minimum of %.2f%%', result.total_coverage, configuration.minimum_coverage)
88
+ failure = true
89
+ end
90
+ # Per-file minimum
91
+ if configuration.per_file_minimum
92
+ result.files.each do |fr|
93
+ if fr.percent < configuration.per_file_minimum
94
+ warn format('File %s coverage %.2f%% is below per-file minimum of %.2f%%', fr.path, fr.percent, configuration.per_file_minimum)
95
+ per_file_failures << fr.path
96
+ end
97
+ end
98
+ failure ||= per_file_failures.any?
99
+ end
100
+
101
+ result.group_coverage.each do |name, pct|
102
+ min = configuration.group_minimum(name)
103
+ if min && pct < min
104
+ warn format('Group "%s" coverage %.2f%% is below the minimum of %.2f%%', name, pct, min)
105
+ failure = true
106
+ end
107
+ end
108
+ exit(2) if failure
109
+ end
110
+
111
+ private
112
+
113
+ def merge_value(a, b)
114
+ ah = normalize_value(a)
115
+ bh = normalize_value(b)
116
+ lines = merge_lines(ah['lines'], bh['lines'])
117
+ branches = merge_branches(ah['branches'], bh['branches'])
118
+ branches ? { 'lines' => lines, 'branches' => branches } : lines
119
+ end
120
+
121
+ def merge_lines(la, lb)
122
+ la ||= []
123
+ lb ||= []
124
+ n = [la.length, lb.length].max
125
+ out = Array.new(n)
126
+ n.times do |i|
127
+ va, vb = la[i], lb[i]
128
+ if va.nil? && vb.nil?
129
+ out[i] = nil
130
+ else
131
+ out[i] = (va || 0).to_i + (vb || 0).to_i
132
+ end
133
+ end
134
+ out
135
+ end
136
+
137
+ def merge_branches(ba, bb)
138
+ return bb || ba unless ba && bb
139
+ { 'covered' => ba['covered'].to_i + bb['covered'].to_i,
140
+ 'total' => ba['total'].to_i + bb['total'].to_i }
141
+ end
142
+
143
+ def normalize_value(v)
144
+ if v.is_a?(Array)
145
+ { 'lines' => v, 'branches' => nil }
146
+ elsif v.is_a?(Hash)
147
+ lines = v['lines'] || v[:lines]
148
+ branches = v['branches'] || v[:branches]
149
+ if branches && !branches.is_a?(Hash) && branches.respond_to?(:to_h)
150
+ branches = branches.to_h
151
+ end
152
+ branches = nil unless branches.nil? || branches.is_a?(Hash)
153
+ { 'lines' => lines, 'branches' => branches }
154
+ else
155
+ { 'lines' => nil, 'branches' => nil }
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ at_exit do
162
+ if Getcov.started?
163
+ Getcov.finalize!
164
+ end
165
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'test_helper'
3
+ require 'open3'
4
+
5
+ class AutoIntegrationTest < Minitest::Test
6
+ def test_auto_starts_and_prints_summary
7
+ ruby = RbConfig.ruby
8
+ cmd = [ruby, '-Ilib', '-r', 'getcov/auto', '-e', 'x=1; y=2; z=x+y;']
9
+ out, err, status = Open3.capture3(*cmd)
10
+ assert_match(/Getcov Coverage Summary/, out)
11
+ assert status.success?, err
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'test_helper'
3
+
4
+ class BranchSummaryTest < Minitest::Test
5
+ def test_branch_summary_is_printed_when_present
6
+ cfg = Getcov::Configuration.new
7
+ raw = {
8
+ File.expand_path('a.rb') => {
9
+ 'lines' => [1, 0, nil],
10
+ 'branches' => { 'covered' => 3, 'total' => 4 }
11
+ }
12
+ }
13
+ result = Getcov::Result.new(raw, cfg)
14
+ out, _ = capture_io { result.print_summary }
15
+ assert_match(/Total Branch Coverage: 75.00%/, out)
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'test_helper'
3
+
4
+ class ConfigurationTest < Minitest::Test
5
+ include TestSupport
6
+
7
+ def test_add_filter_with_regex
8
+ cfg = Getcov::Configuration.new
9
+ cfg.add_filter(%r{(^|/)spec/})
10
+ assert cfg.filtered?('spec/models/user_spec.rb')
11
+ refute cfg.filtered?('app/models/user.rb')
12
+ end
13
+
14
+ def test_add_group_and_group_for
15
+ cfg = Getcov::Configuration.new
16
+ cfg.add_group('Models', %r{^app/models/})
17
+ assert_equal 'Models', cfg.group_for('app/models/user.rb')
18
+ assert_nil cfg.group_for('lib/tasks.rb')
19
+ end
20
+
21
+ def test_track_files_globs
22
+ Dir.mktmpdir do |dir|
23
+ File.write(File.join(dir, 'a.rb'), "puts :a\n")
24
+ File.write(File.join(dir, 'b.rb'), "puts :b\n")
25
+ cfg = Getcov::Configuration.new
26
+ cfg.root = dir
27
+ cfg.track_files '*.rb'
28
+ tracked = cfg.tracked_paths.map { |p| File.basename(p) }.sort
29
+ assert_equal %w[a.rb b.rb], tracked
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'test_helper'
3
+ require 'tmpdir'
4
+ require 'getcov/formatter/cobertura'
5
+ require 'getcov/formatter/lcov'
6
+ require 'getcov/formatter/badge'
7
+
8
+ class FormattersExportTest < Minitest::Test
9
+ def setup
10
+ @cfg = Getcov::Configuration.new
11
+ @cfg.root = Dir.pwd
12
+ end
13
+
14
+ def sample_result
15
+ raw = { File.expand_path('a.rb') => [1, 0, nil] }
16
+ Getcov::Result.new(raw, @cfg)
17
+ end
18
+
19
+ def test_cobertura_and_lcov_and_badge_written
20
+ Dir.mktmpdir do |dir|
21
+ @cfg.output_dir = dir
22
+ res = sample_result
23
+
24
+ Getcov::Formatter::Cobertura.new(@cfg).write(res)
25
+ Getcov::Formatter::LCOV.new(@cfg).write(res)
26
+ Getcov::Formatter::Badge.new(@cfg).write(res)
27
+
28
+ assert File.exist?(File.join(dir, 'coverage.xml')), 'coverage.xml missing'
29
+ assert File.exist?(File.join(dir, 'lcov.info')), 'lcov.info missing'
30
+ assert File.exist?(File.join(dir, 'coverage.svg')), 'coverage.svg missing'
31
+ end
32
+ end
33
+ end