simplecov-cobertura 3.1.2 → 3.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/.github/workflows/build.yml +2 -2
- data/README.md +9 -4
- data/lib/simplecov-cobertura/version.rb +1 -1
- data/lib/simplecov-cobertura.rb +125 -106
- data/simplecov-cobertura.gemspec +11 -11
- data/test/fixtures/sample/lib/sample.rb +28 -0
- data/test/fixtures/sample/test/simplecov_setup.rb +17 -0
- data/test/fixtures/sample/test/test_merged_a.rb +11 -0
- data/test/fixtures/sample/test/test_merged_b.rb +7 -0
- data/test/fixtures/sample/test/test_sample.rb +12 -0
- data/test/integration_test.rb +175 -0
- data/test/simplecov-cobertura_test.rb +36 -27
- metadata +13 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 64fe70b5fa4a2d1864fd64e6a44798cc2e6530bbef43b601e9b2aaa9e1516951
|
|
4
|
+
data.tar.gz: 419fe1ceee7fec9e4737425d177c5a5e2d58f7a78c95170f75b6f7840f4cccbd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d2d7ddbac9c17c4346372ba6eccc9a73d4ec6c90973555d77c8248fe5fc8e08a9b4e7f11ddb5d418b41e561b96c27580255e3b32dfac9f817e5958eacb372b60
|
|
7
|
+
data.tar.gz: fa33a2992ee7eeb5f72017111996f19169519b9c314b0f0262c19eb8cf838fe8b18c834140cebbc54c781d7b4ec31c767188f87795429db8873b7377ac7ac48c
|
data/.github/workflows/build.yml
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
name: Build
|
|
9
9
|
|
|
10
|
-
on: [push, pull_request]
|
|
10
|
+
on: [ push, pull_request ]
|
|
11
11
|
|
|
12
12
|
jobs:
|
|
13
13
|
test:
|
|
@@ -15,7 +15,7 @@ jobs:
|
|
|
15
15
|
runs-on: ubuntu-latest
|
|
16
16
|
strategy:
|
|
17
17
|
matrix:
|
|
18
|
-
ruby-version: [2.5, 2.6, 2.7, '3.0', 3.1, 3.2, 3.3, 3.4, 4.0]
|
|
18
|
+
ruby-version: [ 2.5, 2.6, 2.7, '3.0', 3.1, 3.2, 3.3, 3.4, 4.0 ]
|
|
19
19
|
|
|
20
20
|
steps:
|
|
21
21
|
- uses: actions/checkout@v6
|
data/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
# simplecov-cobertura
|
|
2
|
-
[](https://github.com/jessebs/simplecov-cobertura/actions/workflows/build.yml) [](http://badge.fury.io/rb/simplecov-cobertura)
|
|
1
|
+
# simplecov-cobertura
|
|
3
2
|
|
|
3
|
+
[](https://github.com/jessebs/simplecov-cobertura/actions/workflows/build.yml) [](http://badge.fury.io/rb/simplecov-cobertura)
|
|
4
4
|
|
|
5
|
-
Produces [Cobertura](http://cobertura.sourceforge.net/) formatted XML
|
|
5
|
+
Produces [Cobertura](http://cobertura.sourceforge.net/) formatted XML
|
|
6
|
+
from [SimpleCov](https://github.com/colszowka/simplecov).
|
|
6
7
|
|
|
7
|
-
Output can be consumed by the [Jenkins Cobertura Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Cobertura+Plugin)
|
|
8
|
+
Output can be consumed by the [Jenkins Cobertura Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Cobertura+Plugin)
|
|
9
|
+
for easy
|
|
8
10
|
coverage visualization.
|
|
9
11
|
|
|
10
12
|
## Installation
|
|
@@ -32,7 +34,9 @@ SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
|
|
|
32
34
|
```
|
|
33
35
|
|
|
34
36
|
## Continuous Integration
|
|
37
|
+
|
|
35
38
|
Tested in a CI environment against the following Ruby versions:
|
|
39
|
+
|
|
36
40
|
* 3.0 - 3.4
|
|
37
41
|
* 2.5 - 2.7
|
|
38
42
|
|
|
@@ -45,6 +49,7 @@ Tested in a CI environment against the following Ruby versions:
|
|
|
45
49
|
5. Create a new Pull Request
|
|
46
50
|
|
|
47
51
|
## License
|
|
52
|
+
|
|
48
53
|
Copyright 2025 Jesse Bowes
|
|
49
54
|
|
|
50
55
|
Licensed under the Apache License, Version 2.0 (the "License");
|
data/lib/simplecov-cobertura.rb
CHANGED
|
@@ -34,126 +34,145 @@ module SimpleCov
|
|
|
34
34
|
|
|
35
35
|
private
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
37
|
+
def result_to_xml(result)
|
|
38
|
+
doc = REXML::Document.new
|
|
39
|
+
doc.context[:attribute_quote] = :quote
|
|
40
|
+
doc << REXML::XMLDecl.new('1.0')
|
|
41
|
+
doc << REXML::DocType.new('coverage', "SYSTEM \"#{DTD_URL}\"")
|
|
42
|
+
doc << REXML::Comment.new("Generated by simplecov-cobertura version #{VERSION} (https://github.com/jessebs/simplecov-cobertura)")
|
|
43
|
+
doc.add_element REXML::Element.new('coverage')
|
|
44
|
+
coverage = doc.root
|
|
45
|
+
|
|
46
|
+
set_coverage_attributes(coverage, result)
|
|
47
|
+
|
|
48
|
+
coverage.add_element(sources = REXML::Element.new('sources'))
|
|
49
|
+
sources.add_element(source = REXML::Element.new('source'))
|
|
50
|
+
source.text = SimpleCov.root
|
|
51
|
+
|
|
52
|
+
coverage.add_element(packages = REXML::Element.new('packages'))
|
|
53
|
+
|
|
54
|
+
if result.groups.empty?
|
|
55
|
+
groups = { File.basename(SimpleCov.root) => result.files }
|
|
56
|
+
else
|
|
57
|
+
groups = result.groups
|
|
58
|
+
end
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
groups.each do |name, files|
|
|
61
|
+
next if files.empty?
|
|
62
|
+
packages.add_element(package = REXML::Element.new('package'))
|
|
63
|
+
set_package_attributes(package, name, files)
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
package.add_element(classes = REXML::Element.new('classes'))
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
files.each do |file|
|
|
68
|
+
classes.add_element(class_ = REXML::Element.new('class'))
|
|
69
|
+
set_class_attributes(class_, file)
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
class_.add_element(REXML::Element.new('methods'))
|
|
72
|
+
class_.add_element(lines = REXML::Element.new('lines'))
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
branches_by_line[line_no] ||= { total: 0, covered: 0 }
|
|
79
|
-
branches_by_line[line_no][:total] += 1
|
|
80
|
-
branches_by_line[line_no][:covered] += 1 if branch.covered?
|
|
81
|
-
end
|
|
82
|
-
end
|
|
74
|
+
branches_by_line = {}
|
|
75
|
+
if SimpleCov.branch_coverage?
|
|
76
|
+
build_branches_by_line(file, branches_by_line)
|
|
77
|
+
end
|
|
83
78
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
end
|
|
79
|
+
file.lines.each do |file_line|
|
|
80
|
+
if file_line.covered? || file_line.missed?
|
|
81
|
+
lines.add_element(line = REXML::Element.new('line'))
|
|
82
|
+
set_line_attributes(line, file_line)
|
|
83
|
+
set_branch_attributes(line, file_line, branches_by_line) if SimpleCov.branch_coverage?
|
|
90
84
|
end
|
|
91
85
|
end
|
|
92
86
|
end
|
|
93
|
-
|
|
94
|
-
doc
|
|
95
87
|
end
|
|
96
88
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
coverage.attributes['
|
|
110
|
-
coverage.attributes['
|
|
111
|
-
coverage.attributes['timestamp'] = Time.now.to_i.to_s
|
|
89
|
+
doc
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def set_coverage_attributes(coverage, result)
|
|
93
|
+
ls = result.coverage_statistics[:line]
|
|
94
|
+
bs = result.coverage_statistics[:branch]
|
|
95
|
+
|
|
96
|
+
coverage.attributes['line-rate'] = extract_rate(ls.percent)
|
|
97
|
+
coverage.attributes['lines-covered'] = ls.covered.to_s.to_s
|
|
98
|
+
coverage.attributes['lines-valid'] = ls.total.to_s.to_s
|
|
99
|
+
if SimpleCov.branch_coverage?
|
|
100
|
+
coverage.attributes['branches-covered'] = bs.covered.to_s
|
|
101
|
+
coverage.attributes['branches-valid'] = bs.total.to_s
|
|
102
|
+
coverage.attributes['branch-rate'] = extract_rate(bs.percent)
|
|
112
103
|
end
|
|
104
|
+
coverage.attributes['complexity'] = '0'
|
|
105
|
+
coverage.attributes['version'] = '0'
|
|
106
|
+
coverage.attributes['timestamp'] = Time.now.to_i.to_s
|
|
107
|
+
end
|
|
113
108
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
def set_package_attributes(package, name, result)
|
|
110
|
+
ls = result.coverage_statistics[:line]
|
|
111
|
+
bs = result.coverage_statistics[:branch]
|
|
117
112
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
end
|
|
123
|
-
package.attributes['complexity'] = '0'
|
|
113
|
+
package.attributes['name'] = name
|
|
114
|
+
package.attributes['line-rate'] = extract_rate(ls.percent)
|
|
115
|
+
if SimpleCov.branch_coverage?
|
|
116
|
+
package.attributes['branch-rate'] = extract_rate(bs.percent)
|
|
124
117
|
end
|
|
118
|
+
package.attributes['complexity'] = '0'
|
|
119
|
+
end
|
|
125
120
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
def set_class_attributes(class_, file)
|
|
122
|
+
ls = file.coverage_statistics[:line]
|
|
123
|
+
bs = file.coverage_statistics[:branch]
|
|
129
124
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
end
|
|
137
|
-
class_.attributes['complexity'] = '0'
|
|
125
|
+
filename = file.filename
|
|
126
|
+
class_.attributes['name'] = resolve_filename(filename)
|
|
127
|
+
class_.attributes['filename'] = resolve_filename(filename)
|
|
128
|
+
class_.attributes['line-rate'] = extract_rate(ls.percent)
|
|
129
|
+
if SimpleCov.branch_coverage?
|
|
130
|
+
class_.attributes['branch-rate'] = extract_rate(bs.percent)
|
|
138
131
|
end
|
|
132
|
+
class_.attributes['complexity'] = '0'
|
|
133
|
+
end
|
|
139
134
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
135
|
+
def set_line_attributes(line, file_line)
|
|
136
|
+
line.attributes['number'] = file_line.line_number.to_s
|
|
137
|
+
line.attributes['hits'] = file_line.coverage.to_s
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def set_branch_attributes(line, file_line, branches_by_line)
|
|
141
|
+
branch_info = branches_by_line[file_line.line_number]
|
|
142
|
+
if branch_info
|
|
143
|
+
total = branch_info[:total]
|
|
144
|
+
covered = branch_info[:covered]
|
|
145
|
+
pct_coverage = total > 0 ? (covered * 100 / total) : 0
|
|
146
|
+
line.attributes['branch'] = 'true'
|
|
147
|
+
line.attributes['condition-coverage'] = "#{pct_coverage}% (#{covered}/#{total})"
|
|
148
|
+
else
|
|
149
|
+
line.attributes['branch'] = 'false'
|
|
143
150
|
end
|
|
151
|
+
end
|
|
144
152
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
line.attributes['branch'] = 'false'
|
|
153
|
+
def build_branches_by_line(file, branches_by_line)
|
|
154
|
+
file.coverage_data.fetch("branches", {}).each do |condition, branches|
|
|
155
|
+
line = condition_start_line(condition)
|
|
156
|
+
next unless line
|
|
157
|
+
|
|
158
|
+
info = branches_by_line[line] ||= { total: 0, covered: 0 }
|
|
159
|
+
branches.each_value do |hit_count|
|
|
160
|
+
info[:total] += 1
|
|
161
|
+
info[:covered] += 1 if hit_count.to_i > 0
|
|
155
162
|
end
|
|
156
163
|
end
|
|
164
|
+
rescue StandardError => e
|
|
165
|
+
warn "simplecov-cobertura: couldn't extract per-line branch detail " \
|
|
166
|
+
"(#{e.class}: #{e.message}); lines will report branch=\"false\""
|
|
167
|
+
branches_by_line.clear
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def condition_start_line(condition)
|
|
171
|
+
return condition[2] if condition.is_a?(Array)
|
|
172
|
+
return nil unless condition.is_a?(String)
|
|
173
|
+
|
|
174
|
+
condition.scan(/-?\d+/)[1]&.to_i # [id, start_line, ...]
|
|
175
|
+
end
|
|
157
176
|
|
|
158
177
|
# Roughly mirrors private method SimpleCov::Formatter::HTMLFormatter#output_coverage
|
|
159
178
|
def output_message(result, output_path)
|
|
@@ -163,17 +182,17 @@ module SimpleCov
|
|
|
163
182
|
output
|
|
164
183
|
end
|
|
165
184
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
185
|
+
def resolve_filename(filename)
|
|
186
|
+
Pathname.new(filename).relative_path_from(project_root).to_s
|
|
187
|
+
end
|
|
169
188
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
189
|
+
def extract_rate(percent)
|
|
190
|
+
(percent / 100).round(4).to_s
|
|
191
|
+
end
|
|
173
192
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
193
|
+
def project_root
|
|
194
|
+
@project_root ||= Pathname.new(SimpleCov.root)
|
|
195
|
+
end
|
|
177
196
|
end
|
|
178
197
|
end
|
|
179
198
|
end
|
data/simplecov-cobertura.gemspec
CHANGED
|
@@ -5,19 +5,19 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
|
5
5
|
require 'simplecov-cobertura/version'
|
|
6
6
|
|
|
7
7
|
Gem::Specification.new do |spec|
|
|
8
|
-
spec.name
|
|
9
|
-
spec.version
|
|
10
|
-
spec.authors
|
|
11
|
-
spec.email
|
|
12
|
-
spec.summary
|
|
13
|
-
spec.description
|
|
14
|
-
spec.homepage
|
|
15
|
-
spec.license
|
|
8
|
+
spec.name = 'simplecov-cobertura'
|
|
9
|
+
spec.version = SimpleCov::Formatter::CoberturaFormatter::VERSION
|
|
10
|
+
spec.authors = ['Jesse Bowes']
|
|
11
|
+
spec.email = ['jessebowes@acm.org']
|
|
12
|
+
spec.summary = 'SimpleCov Cobertura Formatter'
|
|
13
|
+
spec.description = 'Produces Cobertura XML formatted output from SimpleCov'
|
|
14
|
+
spec.homepage = 'https://github.com/jessebs/simplecov-cobertura'
|
|
15
|
+
spec.license = 'Apache-2.0'
|
|
16
16
|
spec.required_ruby_version = '>= 2.5.0'
|
|
17
17
|
|
|
18
|
-
spec.files
|
|
19
|
-
spec.executables
|
|
20
|
-
spec.test_files
|
|
18
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
21
21
|
spec.require_paths = ['lib']
|
|
22
22
|
|
|
23
23
|
spec.add_development_dependency 'test-unit', '~> 3.2'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class Sample
|
|
2
|
+
def greet(name)
|
|
3
|
+
if name.nil?
|
|
4
|
+
"Hello, stranger!"
|
|
5
|
+
else
|
|
6
|
+
"Hello, #{name}!"
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def absolute(n)
|
|
11
|
+
if n < 0
|
|
12
|
+
-n
|
|
13
|
+
else
|
|
14
|
+
n
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def unused_method
|
|
19
|
+
x = 1
|
|
20
|
+
y = 2
|
|
21
|
+
z = x + y
|
|
22
|
+
if z > 0
|
|
23
|
+
"positive"
|
|
24
|
+
else
|
|
25
|
+
"non-positive"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
$LOAD_PATH.unshift(File.join(ENV.fetch('GEM_ROOT'), 'lib'))
|
|
2
|
+
|
|
3
|
+
require 'simplecov'
|
|
4
|
+
require 'simplecov-cobertura'
|
|
5
|
+
|
|
6
|
+
SimpleCov.command_name ENV.fetch('COMMAND_NAME', 'Unit Tests')
|
|
7
|
+
SimpleCov.start do
|
|
8
|
+
enable_coverage :branch
|
|
9
|
+
use_merging true
|
|
10
|
+
merge_timeout 3600 # generous so slow CI can't expire run A's result
|
|
11
|
+
root ENV.fetch('PROJECT_ROOT')
|
|
12
|
+
coverage_dir 'coverage'
|
|
13
|
+
formatter SimpleCov::Formatter::CoberturaFormatter
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
require File.join(ENV.fetch('PROJECT_ROOT'), 'lib', 'sample')
|
|
17
|
+
require 'test/unit'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require_relative 'simplecov_setup'
|
|
2
|
+
|
|
3
|
+
class MergedATest < Test::Unit::TestCase
|
|
4
|
+
def test_greet_with_name
|
|
5
|
+
assert_equal 'Hello, World!', Sample.new.greet('World') # greet: else branch
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_absolute_positive
|
|
9
|
+
assert_equal 5, Sample.new.absolute(5) # absolute: else branch
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require_relative 'simplecov_setup'
|
|
2
|
+
|
|
3
|
+
class SampleTest < Test::Unit::TestCase
|
|
4
|
+
def test_greet_with_name
|
|
5
|
+
assert_equal "Hello, World!", Sample.new.greet("World")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_absolute_positive
|
|
9
|
+
# Only exercises the else branch (n >= 0), never the then branch (n < 0)
|
|
10
|
+
assert_equal 5, Sample.new.absolute(5)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'tmpdir'
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'nokogiri'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
|
|
7
|
+
class IntegrationTest < Test::Unit::TestCase
|
|
8
|
+
FIXTURES_DIR = File.join(__dir__, 'fixtures')
|
|
9
|
+
GEM_ROOT = File.expand_path('..', __dir__)
|
|
10
|
+
|
|
11
|
+
def setup
|
|
12
|
+
@tmpdir = Dir.mktmpdir('simplecov-cobertura-integration')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def teardown
|
|
16
|
+
FileUtils.remove_entry(@tmpdir) if @tmpdir && Dir.exist?(@tmpdir)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_clean_load_require_and_assign_formatter
|
|
20
|
+
# In a fresh subprocess, require simplecov-cobertura and assign the formatter.
|
|
21
|
+
# This catches missing requires (e.g. simplecov, stringio).
|
|
22
|
+
code = <<~RUBY
|
|
23
|
+
require 'simplecov-cobertura'
|
|
24
|
+
SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
|
|
25
|
+
RUBY
|
|
26
|
+
stdout, stderr, status = Open3.capture3('ruby', '-e', code)
|
|
27
|
+
assert status.success?, "Clean-load subprocess failed.\nstdout: #{stdout}\nstderr: #{stderr}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_real_coverage_run_produces_valid_cobertura_xml
|
|
31
|
+
install_fixture('sample')
|
|
32
|
+
|
|
33
|
+
# Run the sample test in a subprocess.
|
|
34
|
+
stdout, stderr, status = run_fixture_test('test/test_sample.rb')
|
|
35
|
+
assert status.success?, "Sample test subprocess failed.\nstdout: #{stdout}\nstderr: #{stderr}"
|
|
36
|
+
|
|
37
|
+
# --- Assert on the generated coverage.xml ---
|
|
38
|
+
coverage_xml_path = File.join(@tmpdir, 'coverage', 'coverage.xml')
|
|
39
|
+
assert File.exist?(coverage_xml_path), "coverage.xml was not generated"
|
|
40
|
+
xml_content = File.read(coverage_xml_path)
|
|
41
|
+
assert !xml_content.empty?, "coverage.xml is empty"
|
|
42
|
+
|
|
43
|
+
doc = Nokogiri::XML(xml_content) { |config| config.strict }
|
|
44
|
+
assert doc.errors.empty?, "XML parse errors: #{doc.errors.join(', ')}"
|
|
45
|
+
|
|
46
|
+
# Root element
|
|
47
|
+
coverage = doc.at_xpath('/coverage')
|
|
48
|
+
assert_not_nil coverage, "Missing /coverage root element"
|
|
49
|
+
|
|
50
|
+
# Line coverage attributes — partial, not 100% and not 0%
|
|
51
|
+
line_rate = coverage['line-rate'].to_f
|
|
52
|
+
assert line_rate > 0.0, "line-rate should be > 0, got #{line_rate}"
|
|
53
|
+
assert line_rate < 1.0, "line-rate should be < 1.0 (partial coverage), got #{line_rate}"
|
|
54
|
+
|
|
55
|
+
lines_covered = coverage['lines-covered'].to_i
|
|
56
|
+
lines_valid = coverage['lines-valid'].to_i
|
|
57
|
+
assert lines_covered > 0, "lines-covered should be > 0"
|
|
58
|
+
assert lines_valid > lines_covered, "lines-valid (#{lines_valid}) should be > lines-covered (#{lines_covered})"
|
|
59
|
+
|
|
60
|
+
# Branch coverage attributes — partial
|
|
61
|
+
branch_rate = coverage['branch-rate'].to_f
|
|
62
|
+
assert branch_rate > 0.0, "branch-rate should be > 0, got #{branch_rate}"
|
|
63
|
+
assert branch_rate < 1.0, "branch-rate should be < 1.0 (partial coverage), got #{branch_rate}"
|
|
64
|
+
|
|
65
|
+
branches_covered = coverage['branches-covered'].to_i
|
|
66
|
+
branches_valid = coverage['branches-valid'].to_i
|
|
67
|
+
assert branches_covered > 0, "branches-covered should be > 0"
|
|
68
|
+
assert branches_valid > branches_covered, "branches-valid (#{branches_valid}) should be > branches-covered (#{branches_covered})"
|
|
69
|
+
|
|
70
|
+
# The sample library file appears as a class with the expected relative filename
|
|
71
|
+
classes = doc.xpath('//class')
|
|
72
|
+
filenames = classes.map { |c| c['filename'] }
|
|
73
|
+
assert filenames.any? { |f| f.include?('lib/sample.rb') },
|
|
74
|
+
"Expected a class with filename containing 'lib/sample.rb', got: #{filenames}"
|
|
75
|
+
|
|
76
|
+
# Line-level <line> elements exist with hits and branch attributes
|
|
77
|
+
lines = doc.xpath('//line')
|
|
78
|
+
assert lines.length > 0, "Expected <line> elements in the output"
|
|
79
|
+
lines.each do |line|
|
|
80
|
+
assert_not_nil line['number'], "Line element missing 'number' attribute"
|
|
81
|
+
assert_not_nil line['hits'], "Line element missing 'hits' attribute"
|
|
82
|
+
assert_not_nil line['branch'], "Line element missing 'branch' attribute"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# At least one line should have branch='true' (from the if/else)
|
|
86
|
+
branch_lines = lines.select { |l| l['branch'] == 'true' }
|
|
87
|
+
assert branch_lines.length > 0, "Expected at least one line with branch='true'"
|
|
88
|
+
|
|
89
|
+
# Validate condition-coverage on branch lines
|
|
90
|
+
branch_lines.each do |bl|
|
|
91
|
+
cc = bl['condition-coverage']
|
|
92
|
+
assert_not_nil cc, "Branch line missing 'condition-coverage' attribute"
|
|
93
|
+
assert_match(/\d+% \(\d+\/\d+\)/, cc, "condition-coverage format unexpected: #{cc}")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_condition_coverage_values_are_accurate
|
|
98
|
+
install_fixture('sample')
|
|
99
|
+
|
|
100
|
+
stdout, stderr, status = run_fixture_test('test/test_sample.rb')
|
|
101
|
+
assert status.success?, "Sample test subprocess failed.\nstdout: #{stdout}\nstderr: #{stderr}"
|
|
102
|
+
|
|
103
|
+
coverage_xml_path = File.join(@tmpdir, 'coverage', 'coverage.xml')
|
|
104
|
+
assert File.exist?(coverage_xml_path), "coverage.xml was not generated"
|
|
105
|
+
doc = Nokogiri::XML(File.read(coverage_xml_path)) { |config| config.strict }
|
|
106
|
+
|
|
107
|
+
# Find the branch line for the if statement in sample.rb's absolute method
|
|
108
|
+
sample_class = doc.xpath('//class').find { |c| c['filename'].include?('sample.rb') }
|
|
109
|
+
assert_not_nil sample_class, "Expected sample.rb class in output"
|
|
110
|
+
|
|
111
|
+
branch_lines = sample_class.xpath('.//line[@branch="true"]')
|
|
112
|
+
assert branch_lines.length > 0, "Expected branch lines in sample.rb"
|
|
113
|
+
|
|
114
|
+
# Each if/else has 2 total branches. greet and absolute each have 1/2 covered,
|
|
115
|
+
# unused_method has 0/2 covered. Verify exact values by line number.
|
|
116
|
+
condition_coverages = branch_lines.map { |bl| [bl['number'].to_i, bl['condition-coverage']] }.to_h
|
|
117
|
+
|
|
118
|
+
# greet: line 3 — only else branch taken => 50% (1/2)
|
|
119
|
+
assert_equal '50% (1/2)', condition_coverages[3], "greet condition-coverage mismatch"
|
|
120
|
+
# absolute: line 11 — only else branch taken => 50% (1/2)
|
|
121
|
+
assert_equal '50% (1/2)', condition_coverages[11], "absolute condition-coverage mismatch"
|
|
122
|
+
# unused_method: line 22 — neither branch taken => 0% (0/2)
|
|
123
|
+
assert_equal '0% (0/2)', condition_coverages[22], "unused_method condition-coverage mismatch"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_merged_results_exercise_string_condition_keys
|
|
127
|
+
install_fixture('sample')
|
|
128
|
+
|
|
129
|
+
stdout, stderr, status = run_fixture_test('test/test_merged_a.rb',
|
|
130
|
+
'COMMAND_NAME' => 'Run A')
|
|
131
|
+
assert status.success?, "Run A failed.\nstdout: #{stdout}\nstderr: #{stderr}"
|
|
132
|
+
|
|
133
|
+
# Tripwire: confirm the stored resultset actually contains stringified
|
|
134
|
+
# condition keys. If a future simplecov changes its serialization, this
|
|
135
|
+
# assertion fails and tells you this test no longer covers the string path.
|
|
136
|
+
resultset = File.read(File.join(@tmpdir, 'coverage', '.resultset.json'))
|
|
137
|
+
assert_match(/\[:if, \d+, \d+/, resultset,
|
|
138
|
+
'Expected stringified branch condition keys in .resultset.json')
|
|
139
|
+
|
|
140
|
+
stdout, stderr, status = run_fixture_test('test/test_merged_b.rb',
|
|
141
|
+
'COMMAND_NAME' => 'Run B')
|
|
142
|
+
assert status.success?, "Run B failed.\nstdout: #{stdout}\nstderr: #{stderr}"
|
|
143
|
+
|
|
144
|
+
doc = Nokogiri::XML(File.read(File.join(@tmpdir, 'coverage', 'coverage.xml'))) { |c| c.strict }
|
|
145
|
+
sample_class = doc.xpath('//class').find { |c| c['filename'].include?('sample.rb') }
|
|
146
|
+
assert_not_nil sample_class
|
|
147
|
+
|
|
148
|
+
cc = sample_class.xpath('.//line[@branch="true"]')
|
|
149
|
+
.map { |l| [l['number'].to_i, l['condition-coverage']] }
|
|
150
|
+
.to_h
|
|
151
|
+
|
|
152
|
+
# greet (line 3): else in Run A + then in Run B => merged 2/2.
|
|
153
|
+
# Proves string keys were parsed AND hit counts summed across runs.
|
|
154
|
+
assert_equal '100% (2/2)', cc[3], 'greet should be fully covered after merge'
|
|
155
|
+
# absolute (line 11): only Run A touched it => still 1/2.
|
|
156
|
+
assert_equal '50% (1/2)', cc[11]
|
|
157
|
+
# unused_method (line 22): never called in either run => 0/2.
|
|
158
|
+
assert_equal '0% (0/2)', cc[22]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
def install_fixture(name)
|
|
164
|
+
FileUtils.cp_r(File.join(FIXTURES_DIR, name, '.'), @tmpdir)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def run_fixture_test(test_path, extra_env = {})
|
|
168
|
+
Open3.capture3(
|
|
169
|
+
{ 'GEM_ROOT' => GEM_ROOT, 'PROJECT_ROOT' => @tmpdir }.merge(extra_env),
|
|
170
|
+
'ruby', File.join(@tmpdir, test_path),
|
|
171
|
+
chdir: @tmpdir
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
end
|
|
@@ -9,24 +9,24 @@ class CoberturaFormatterTest < Test::Unit::TestCase
|
|
|
9
9
|
SimpleCov.enable_coverage :branch
|
|
10
10
|
SimpleCov.coverage_dir "tmp"
|
|
11
11
|
@result = SimpleCov::Result.new({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
12
|
+
"#{__FILE__}" => {
|
|
13
|
+
"lines" => [1, 1, 1, nil, 1, nil, 1, 0, nil, 1, nil, nil, nil],
|
|
14
|
+
"branches" => {
|
|
15
|
+
[:if, 0, 3, 4, 3, 21] =>
|
|
16
|
+
{ [:then, 1, 3, 4, 3, 10] => 0, [:else, 2, 3, 4, 3, 21] => 1 },
|
|
17
|
+
[:if, 3, 5, 4, 5, 26] =>
|
|
18
|
+
{ [:then, 4, 5, 16, 5, 20] => 1, [:else, 5, 5, 23, 5, 26] => 0 },
|
|
19
|
+
[:if, 6, 7, 4, 11, 7] =>
|
|
20
|
+
{ [:then, 7, 8, 6, 8, 10] => 0, [:else, 8, 10, 6, 10, 9] => 1 },
|
|
21
|
+
[:if, 9, 12, 4, 12, 15] =>
|
|
22
|
+
{ [:then, 10, 12, 6, 12, 10] => 1, [:else, 11, 12, 13, 12, 15] => 0 },
|
|
23
|
+
[:if, 12, 13, 4, 13, 20] =>
|
|
24
|
+
{ [:then, 13, 13, 6, 13, 15] => 1, [:else, 14, 13, 18, 13, 20] => 0 },
|
|
25
|
+
[:if, 15, 15, 4, 15, 25] =>
|
|
26
|
+
{ [:then, 16, 15, 6, 15, 20] => 0, [:else, 17, 15, 23, 15, 25] => 0 }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
30
|
@formatter = SimpleCov::Formatter::CoberturaFormatter.new
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -110,20 +110,21 @@ class CoberturaFormatterTest < Test::Unit::TestCase
|
|
|
110
110
|
assert_equal '1', first_line.attribute('hits').value
|
|
111
111
|
last_line = lines.last
|
|
112
112
|
assert_equal '10', last_line.attribute('number').value
|
|
113
|
-
assert_equal '
|
|
113
|
+
assert_equal 'false', last_line.attribute('branch').value
|
|
114
114
|
assert_equal '1', last_line.attribute('hits').value
|
|
115
115
|
|
|
116
|
-
# Verify condition-coverage accurately reflects branch counts per line
|
|
116
|
+
# Verify condition-coverage accurately reflects branch counts per condition line
|
|
117
117
|
branched_lines = lines.select { |l| l.attribute('branch').value == 'true' }
|
|
118
118
|
condition_coverages = branched_lines.map { |l| [l.attribute('number').value, l.attribute('condition-coverage').value] }
|
|
119
|
-
# Line 3: 2 branches (then=>0, else=>1) => 50% (1/2)
|
|
119
|
+
# Line 3: condition [:if, 0, 3, ...] with 2 branches (then=>0, else=>1) => 50% (1/2)
|
|
120
120
|
assert_include condition_coverages, ['3', '50% (1/2)']
|
|
121
|
-
# Line 5: 2 branches (then=>1, else=>0) => 50% (1/2)
|
|
121
|
+
# Line 5: condition [:if, 3, 5, ...] with 2 branches (then=>1, else=>0) => 50% (1/2)
|
|
122
122
|
assert_include condition_coverages, ['5', '50% (1/2)']
|
|
123
|
-
# Line
|
|
124
|
-
assert_include condition_coverages, ['
|
|
125
|
-
#
|
|
126
|
-
|
|
123
|
+
# Line 7: condition [:if, 6, 7, ...] with 2 branches (then=>0, else=>1) => 50% (1/2)
|
|
124
|
+
assert_include condition_coverages, ['7', '50% (1/2)']
|
|
125
|
+
# Lines 12, 13, 15 have nil line coverage so they don't get <line> elements,
|
|
126
|
+
# but their conditions are still correctly grouped by condition start line.
|
|
127
|
+
assert_equal 3, branched_lines.length
|
|
127
128
|
end
|
|
128
129
|
|
|
129
130
|
def test_groups
|
|
@@ -172,7 +173,7 @@ class CoberturaFormatterTest < Test::Unit::TestCase
|
|
|
172
173
|
assert_equal '1', first_line.attribute('hits').value
|
|
173
174
|
last_line = lines.last
|
|
174
175
|
assert_equal '10', last_line.attribute('number').value
|
|
175
|
-
assert_equal '
|
|
176
|
+
assert_equal 'false', last_line.attribute('branch').value
|
|
176
177
|
assert_equal '1', last_line.attribute('hits').value
|
|
177
178
|
end
|
|
178
179
|
|
|
@@ -192,4 +193,12 @@ class CoberturaFormatterTest < Test::Unit::TestCase
|
|
|
192
193
|
ensure
|
|
193
194
|
SimpleCov.root(old_root)
|
|
194
195
|
end
|
|
196
|
+
|
|
197
|
+
def test_condition_start_line_handles_both_key_forms
|
|
198
|
+
formatter = SimpleCov::Formatter::CoberturaFormatter.new
|
|
199
|
+
assert_equal 3, formatter.send(:condition_start_line, [:if, 0, 3, 4, 5, 10])
|
|
200
|
+
assert_equal 3, formatter.send(:condition_start_line, '[:if, 0, 3, 4, 5, 10]')
|
|
201
|
+
assert_equal 7, formatter.send(:condition_start_line, '[:case, 12, 7, 0, 9, 3]')
|
|
202
|
+
assert_nil formatter.send(:condition_start_line, 42)
|
|
203
|
+
end
|
|
195
204
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: simplecov-cobertura
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jesse Bowes
|
|
@@ -95,6 +95,12 @@ files:
|
|
|
95
95
|
- lib/simplecov-cobertura.rb
|
|
96
96
|
- lib/simplecov-cobertura/version.rb
|
|
97
97
|
- simplecov-cobertura.gemspec
|
|
98
|
+
- test/fixtures/sample/lib/sample.rb
|
|
99
|
+
- test/fixtures/sample/test/simplecov_setup.rb
|
|
100
|
+
- test/fixtures/sample/test/test_merged_a.rb
|
|
101
|
+
- test/fixtures/sample/test/test_merged_b.rb
|
|
102
|
+
- test/fixtures/sample/test/test_sample.rb
|
|
103
|
+
- test/integration_test.rb
|
|
98
104
|
- test/simplecov-cobertura_test.rb
|
|
99
105
|
homepage: https://github.com/jessebs/simplecov-cobertura
|
|
100
106
|
licenses:
|
|
@@ -118,4 +124,10 @@ rubygems_version: 3.6.9
|
|
|
118
124
|
specification_version: 4
|
|
119
125
|
summary: SimpleCov Cobertura Formatter
|
|
120
126
|
test_files:
|
|
127
|
+
- test/fixtures/sample/lib/sample.rb
|
|
128
|
+
- test/fixtures/sample/test/simplecov_setup.rb
|
|
129
|
+
- test/fixtures/sample/test/test_merged_a.rb
|
|
130
|
+
- test/fixtures/sample/test/test_merged_b.rb
|
|
131
|
+
- test/fixtures/sample/test/test_sample.rb
|
|
132
|
+
- test/integration_test.rb
|
|
121
133
|
- test/simplecov-cobertura_test.rb
|