ashtonw-slather 1.8.2
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/.coveralls.yml +1 -0
- data/.gitignore +45 -0
- data/.travis.yml +19 -0
- data/CHANGELOG.md +132 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +206 -0
- data/Rakefile +1 -0
- data/assets/highlight.pack.js +1 -0
- data/assets/list.min.js +1 -0
- data/assets/slather.css +316 -0
- data/bin/slather +117 -0
- data/docs/logo.jpg +0 -0
- data/lib/cocoapods_plugin.rb +10 -0
- data/lib/slather.rb +20 -0
- data/lib/slather/coverage_file.rb +195 -0
- data/lib/slather/coverage_service/cobertura_xml_output.rb +183 -0
- data/lib/slather/coverage_service/coveralls.rb +186 -0
- data/lib/slather/coverage_service/gutter_json_output.rb +50 -0
- data/lib/slather/coverage_service/hardcover.rb +61 -0
- data/lib/slather/coverage_service/html_output.rb +244 -0
- data/lib/slather/coverage_service/simple_output.rb +31 -0
- data/lib/slather/coveralls_coverage_file.rb +13 -0
- data/lib/slather/project.rb +132 -0
- data/lib/slather/version.rb +3 -0
- data/slather.gemspec +32 -0
- data/spec/fixtures/fixtures.xcodeproj/project.pbxproj +496 -0
- data/spec/fixtures/fixtures.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- data/spec/fixtures/fixtures.xcodeproj/xcshareddata/xcschemes/fixtures.xcscheme +69 -0
- data/spec/fixtures/fixtures/Supporting Files/fixtures-Prefix.pch +9 -0
- data/spec/fixtures/fixtures/fixtures.h +16 -0
- data/spec/fixtures/fixtures/fixtures.m +23 -0
- data/spec/fixtures/fixtures/fixtures_cpp.cpp +9 -0
- data/spec/fixtures/fixtures/fixtures_cpp.h +6 -0
- data/spec/fixtures/fixtures/fixtures_m.h +5 -0
- data/spec/fixtures/fixtures/fixtures_m.m +5 -0
- data/spec/fixtures/fixtures/fixtures_mm.h +5 -0
- data/spec/fixtures/fixtures/fixtures_mm.mm +5 -0
- data/spec/fixtures/fixtures/more_files/Branches.h +15 -0
- data/spec/fixtures/fixtures/more_files/Branches.m +45 -0
- data/spec/fixtures/fixtures/more_files/Empty.h +13 -0
- data/spec/fixtures/fixtures/more_files/Empty.m +13 -0
- data/spec/fixtures/fixtures/more_files/peekaview.h +13 -0
- data/spec/fixtures/fixtures/more_files/peekaview.m +31 -0
- data/spec/fixtures/fixturesTests/BranchesTests.m +38 -0
- data/spec/fixtures/fixturesTests/Supporting Files/en.lproj/InfoPlist.strings +2 -0
- data/spec/fixtures/fixturesTests/Supporting Files/fixturesTests-Info.plist +22 -0
- data/spec/fixtures/fixturesTests/fixturesTests.m +36 -0
- data/spec/fixtures/fixturesTests/peekaviewTests.m +34 -0
- data/spec/fixtures/fixtures_html/Branches.m.html +261 -0
- data/spec/fixtures/fixtures_html/BranchesTests.m.html +228 -0
- data/spec/fixtures/fixtures_html/Empty.m.html +30 -0
- data/spec/fixtures/fixtures_html/fixtures.m.html +151 -0
- data/spec/fixtures/fixtures_html/fixturesTests.m.html +216 -0
- data/spec/fixtures/fixtures_html/fixtures_cpp.cpp.html +30 -0
- data/spec/fixtures/fixtures_html/fixtures_m.m.html +30 -0
- data/spec/fixtures/fixtures_html/fixtures_mm.mm.html +30 -0
- data/spec/fixtures/fixtures_html/index.html +134 -0
- data/spec/fixtures/fixtures_html/peekaview.m.html +190 -0
- data/spec/fixtures/fixtures_html/peekaviewTests.m.html +206 -0
- data/spec/fixtures/gutter.json +1 -0
- data/spec/slather/cocoapods_plugin_spec.rb +21 -0
- data/spec/slather/coverage_file_spec.rb +337 -0
- data/spec/slather/coverage_service/cobertura_xml_spec.rb +49 -0
- data/spec/slather/coverage_service/coveralls_spec.rb +122 -0
- data/spec/slather/coverage_service/gutter_json_spec.rb +28 -0
- data/spec/slather/coverage_service/hardcover_spec.rb +87 -0
- data/spec/slather/coverage_service/html_output_spec.rb +179 -0
- data/spec/slather/coverage_service/simple_output_spec.rb +35 -0
- data/spec/slather/fixtures.gcno +0 -0
- data/spec/slather/project_spec.rb +288 -0
- data/spec/spec_helper.rb +27 -0
- metadata +319 -0
data/lib/slather.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'slather/version'
|
|
2
|
+
require 'slather/project'
|
|
3
|
+
require 'slather/coverage_file'
|
|
4
|
+
require 'slather/coveralls_coverage_file'
|
|
5
|
+
require 'slather/coverage_service/cobertura_xml_output'
|
|
6
|
+
require 'slather/coverage_service/coveralls'
|
|
7
|
+
require 'slather/coverage_service/hardcover'
|
|
8
|
+
require 'slather/coverage_service/gutter_json_output'
|
|
9
|
+
require 'slather/coverage_service/simple_output'
|
|
10
|
+
require 'slather/coverage_service/html_output'
|
|
11
|
+
|
|
12
|
+
module Slather
|
|
13
|
+
|
|
14
|
+
Encoding.default_external = "utf-8"
|
|
15
|
+
|
|
16
|
+
def self.prepare_pods(pods)
|
|
17
|
+
Pod::UI.warn("[Slather] prepare_pods is now deprecated. The call to prepare_pods in your Podfile can simply be ommitted.")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
module Slather
|
|
2
|
+
class CoverageFile
|
|
3
|
+
|
|
4
|
+
attr_accessor :project, :gcno_file_pathname
|
|
5
|
+
|
|
6
|
+
def initialize(project, gcno_file_pathname)
|
|
7
|
+
self.project = project
|
|
8
|
+
self.gcno_file_pathname = Pathname(gcno_file_pathname)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def source_file_pathname
|
|
12
|
+
@source_file_pathname ||= begin
|
|
13
|
+
base_filename = gcno_file_pathname.basename.sub_ext("")
|
|
14
|
+
# TODO: Handle Swift
|
|
15
|
+
path = nil
|
|
16
|
+
if project.source_directory
|
|
17
|
+
path = Dir["#{project.source_directory}/**/#{base_filename}.{#{supported_file_extensions.join(",")}}"].first
|
|
18
|
+
path &&= Pathname(path)
|
|
19
|
+
else
|
|
20
|
+
pbx_file = project.files.detect { |pbx_file|
|
|
21
|
+
current_base_filename = pbx_file.real_path.basename
|
|
22
|
+
ext_name = File.extname(current_base_filename.to_s)[1..-1]
|
|
23
|
+
current_base_filename.sub_ext("") == base_filename && supported_file_extensions.include?(ext_name)
|
|
24
|
+
}
|
|
25
|
+
path = pbx_file && pbx_file.real_path
|
|
26
|
+
end
|
|
27
|
+
path
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def source_file
|
|
32
|
+
File.new(source_file_pathname)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def source_data
|
|
36
|
+
source_file.read
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def source_file_pathname_relative_to_repo_root
|
|
40
|
+
source_file_pathname.realpath.relative_path_from(Pathname("./").realpath)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def gcov_data
|
|
44
|
+
@gcov_data ||= begin
|
|
45
|
+
gcov_output = `gcov "#{source_file_pathname}" --object-directory "#{gcno_file_pathname.parent}" --branch-probabilities --branch-counts`
|
|
46
|
+
# Sometimes gcov makes gcov files for Cocoa Touch classes, like NSRange. Ignore and delete later.
|
|
47
|
+
gcov_files_created = gcov_output.scan(/creating '(.+\..+\.gcov)'/)
|
|
48
|
+
|
|
49
|
+
gcov_file_name = "./#{source_file_pathname.basename}.gcov"
|
|
50
|
+
if File.exists?(gcov_file_name)
|
|
51
|
+
gcov_data = File.new(gcov_file_name).read
|
|
52
|
+
else
|
|
53
|
+
gcov_data = ""
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
gcov_files_created.each { |file| FileUtils.rm_f(file) }
|
|
57
|
+
gcov_data
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def line_coverage_data
|
|
62
|
+
unless cleaned_gcov_data.empty?
|
|
63
|
+
first_line_start = cleaned_gcov_data =~ /^\s+(-|#+|[0-9+]):\s+1:/
|
|
64
|
+
|
|
65
|
+
cleaned_gcov_data[first_line_start..-1].split("\n").map do |line|
|
|
66
|
+
coverage_for_line(line)
|
|
67
|
+
end
|
|
68
|
+
else
|
|
69
|
+
[]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def cleaned_gcov_data
|
|
74
|
+
data = gcov_data.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '').gsub(/^function(.*) called [0-9]+ returned [0-9]+% blocks executed(.*)$\r?\n/, '')
|
|
75
|
+
data.gsub(/^branch(.*)$\r?\n/, '')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def coverage_for_line(line)
|
|
79
|
+
line =~ /^(.+?):/
|
|
80
|
+
|
|
81
|
+
match = $1.strip
|
|
82
|
+
case match
|
|
83
|
+
when /[0-9]+/
|
|
84
|
+
match.to_i
|
|
85
|
+
when /#+/
|
|
86
|
+
0
|
|
87
|
+
when "-"
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def num_lines_tested
|
|
93
|
+
line_coverage_data.compact.select { |cd| cd > 0 }.count
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def num_lines_testable
|
|
97
|
+
line_coverage_data.compact.count
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def rate_lines_tested
|
|
101
|
+
if num_lines_testable > 0
|
|
102
|
+
(num_lines_tested / num_lines_testable.to_f)
|
|
103
|
+
else
|
|
104
|
+
0
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def percentage_lines_tested
|
|
109
|
+
if num_lines_testable == 0
|
|
110
|
+
100
|
|
111
|
+
else
|
|
112
|
+
rate_lines_tested * 100
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def branch_coverage_data
|
|
117
|
+
@branch_coverage_data ||= begin
|
|
118
|
+
branch_coverage_data = Hash.new
|
|
119
|
+
|
|
120
|
+
gcov_data.scan(/(^(\s+(-|#+|[0-9]+):\s+[1-9]+:(.*)$\r?\n)(^branch\s+[0-9]+\s+[a-zA-Z0-9]+\s+[a-zA-Z0-9]+$\r?\n)+)+/) do |data|
|
|
121
|
+
lines = data[0].split("\n")
|
|
122
|
+
line_number = lines[0].split(':')[1].strip.to_i
|
|
123
|
+
branch_coverage_data[line_number] = lines[1..-1].map do |line|
|
|
124
|
+
if line.split(' ')[2].strip == "never"
|
|
125
|
+
0
|
|
126
|
+
else
|
|
127
|
+
line.split(' ')[3].strip.to_i
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
branch_coverage_data
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def branch_coverage_data_for_statement_on_line(line_number)
|
|
136
|
+
branch_coverage_data[line_number] || []
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def num_branches_for_statement_on_line(line_number)
|
|
140
|
+
branch_coverage_data_for_statement_on_line(line_number).length
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def num_branch_hits_for_statement_on_line(line_number)
|
|
144
|
+
branch_coverage_data_for_statement_on_line(line_number).count { |hit_count| hit_count > 0 }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def rate_branch_coverage_for_statement_on_line(line_number)
|
|
148
|
+
branch_data = branch_coverage_data_for_statement_on_line(line_number)
|
|
149
|
+
if branch_data.empty?
|
|
150
|
+
0.0
|
|
151
|
+
else
|
|
152
|
+
(num_branch_hits_for_statement_on_line(line_number) / branch_data.length.to_f)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def percentage_branch_coverage_for_statement_on_line(line_number)
|
|
157
|
+
rate_branch_coverage_for_statement_on_line(line_number) * 100
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def num_branches_testable
|
|
161
|
+
branch_coverage_data.keys.reduce(0) do |sum, line_number|
|
|
162
|
+
sum += num_branches_for_statement_on_line(line_number)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def num_branches_tested
|
|
167
|
+
branch_coverage_data.keys.reduce(0) do |sum, line_number|
|
|
168
|
+
sum += num_branch_hits_for_statement_on_line(line_number)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def rate_branches_tested
|
|
173
|
+
if (num_branches_testable > 0)
|
|
174
|
+
(num_branches_tested / num_branches_testable.to_f)
|
|
175
|
+
else
|
|
176
|
+
0.0
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def source_file_basename
|
|
181
|
+
File.basename(source_file_pathname, '.m')
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def ignored?
|
|
185
|
+
project.ignore_list.any? do |ignore|
|
|
186
|
+
File.fnmatch(ignore, source_file_pathname_relative_to_repo_root)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def supported_file_extensions
|
|
191
|
+
["cpp", "mm", "m"]
|
|
192
|
+
end
|
|
193
|
+
private :supported_file_extensions
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
|
|
3
|
+
module Slather
|
|
4
|
+
module CoverageService
|
|
5
|
+
module CoberturaXmlOutput
|
|
6
|
+
|
|
7
|
+
def coverage_file_class
|
|
8
|
+
Slather::CoverageFile
|
|
9
|
+
end
|
|
10
|
+
private :coverage_file_class
|
|
11
|
+
|
|
12
|
+
def post
|
|
13
|
+
cobertura_xml_report = create_xml_report(coverage_files)
|
|
14
|
+
store_report(cobertura_xml_report)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def store_report(report)
|
|
18
|
+
output_file = 'cobertura.xml'
|
|
19
|
+
if output_directory
|
|
20
|
+
FileUtils.mkdir_p(output_directory)
|
|
21
|
+
output_file = File.join(output_directory, output_file)
|
|
22
|
+
end
|
|
23
|
+
File.write(output_file, report.to_s)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def grouped_coverage_files
|
|
27
|
+
groups = Hash.new
|
|
28
|
+
coverage_files.each do |coverage_file|
|
|
29
|
+
path = File.dirname(coverage_file.source_file_pathname_relative_to_repo_root)
|
|
30
|
+
if groups[path] == nil
|
|
31
|
+
groups[path] = Array.new
|
|
32
|
+
end
|
|
33
|
+
groups[path].push(coverage_file)
|
|
34
|
+
end
|
|
35
|
+
groups
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def create_xml_report(coverage_files)
|
|
39
|
+
total_project_lines = 0
|
|
40
|
+
total_project_lines_tested = 0
|
|
41
|
+
total_project_line_rate = '%.16f' % 1.0
|
|
42
|
+
total_project_branches = 0
|
|
43
|
+
total_project_branches_tested = 0
|
|
44
|
+
total_project_branch_rate = '%.16f' % 1.0
|
|
45
|
+
|
|
46
|
+
create_empty_xml_report
|
|
47
|
+
coverage_node = @doc.root
|
|
48
|
+
source_node = @doc.at_css "source"
|
|
49
|
+
source_node.content = Pathname.pwd.to_s
|
|
50
|
+
packages_node = @doc.at_css "packages"
|
|
51
|
+
|
|
52
|
+
# group files by path
|
|
53
|
+
grouped_coverage_files.each do |path , package_coverage_files|
|
|
54
|
+
package_node = Nokogiri::XML::Node.new "package", @doc
|
|
55
|
+
package_node.parent = packages_node
|
|
56
|
+
classes_node = Nokogiri::XML::Node.new "classes", @doc
|
|
57
|
+
classes_node.parent = package_node
|
|
58
|
+
package_node['name'] = path.gsub(/\//, '.')
|
|
59
|
+
|
|
60
|
+
total_package_lines = 0
|
|
61
|
+
total_package_lines_tested = 0
|
|
62
|
+
total_package_lines_rate = '%.16f' % 1.0
|
|
63
|
+
total_package_branches = 0
|
|
64
|
+
total_package_branches_tested = 0
|
|
65
|
+
total_package_branch_rate = '%.16f' % 1.0
|
|
66
|
+
|
|
67
|
+
package_coverage_files.each do |package_coverage_file|
|
|
68
|
+
class_node = create_class_node(package_coverage_file)
|
|
69
|
+
class_node.parent = classes_node
|
|
70
|
+
total_package_lines += package_coverage_file.num_lines_testable
|
|
71
|
+
total_package_lines_tested += package_coverage_file.num_lines_tested
|
|
72
|
+
total_package_branches += package_coverage_file.num_branches_testable
|
|
73
|
+
total_package_branches_tested += package_coverage_file.num_branches_tested
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if (total_package_lines > 0)
|
|
77
|
+
total_package_line_rate = '%.16f' % (total_package_lines_tested / total_package_lines.to_f)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if (total_package_branches > 0)
|
|
81
|
+
total_package_branch_rate = '%.16f' % (total_package_branches_tested / total_package_branches.to_f)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
package_node['line-rate'] = total_package_line_rate
|
|
85
|
+
package_node['branch-rate'] = total_package_branch_rate
|
|
86
|
+
package_node['complexity'] = '0.0'
|
|
87
|
+
|
|
88
|
+
total_project_lines += total_package_lines
|
|
89
|
+
total_project_lines_tested += total_package_lines_tested
|
|
90
|
+
total_project_branches += total_package_branches
|
|
91
|
+
total_project_branches_tested += total_package_branches_tested
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if (total_project_lines > 0)
|
|
95
|
+
total_project_line_rate = '%.16f' % (total_project_lines_tested / total_project_lines.to_f)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if (total_project_branches > 0)
|
|
99
|
+
total_project_branch_rate = '%.16f' % (total_project_branches_tested / total_project_branches.to_f)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
coverage_node['line-rate'] = total_project_line_rate
|
|
103
|
+
coverage_node['branch-rate'] = total_project_branch_rate
|
|
104
|
+
coverage_node['lines-covered'] = total_project_lines_tested
|
|
105
|
+
coverage_node['lines-valid'] = total_project_lines
|
|
106
|
+
coverage_node['branches-covered'] = total_project_branches_tested
|
|
107
|
+
coverage_node['branches-valid'] = total_project_branches
|
|
108
|
+
coverage_node['complexity'] = "0.0"
|
|
109
|
+
coverage_node['timestamp'] = DateTime.now.strftime('%s')
|
|
110
|
+
coverage_node['version'] = "Slather #{Slather::VERSION}"
|
|
111
|
+
@doc.to_xml
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def create_class_node(coverage_file)
|
|
115
|
+
filename = coverage_file.source_file_basename
|
|
116
|
+
filepath = coverage_file.source_file_pathname_relative_to_repo_root.to_s
|
|
117
|
+
|
|
118
|
+
class_node = Nokogiri::XML::Node.new "class", @doc
|
|
119
|
+
class_node['name'] = filename
|
|
120
|
+
class_node['filename'] = filepath
|
|
121
|
+
class_node['line-rate'] = '%.16f' % [(coverage_file.num_lines_testable > 0) ? coverage_file.rate_lines_tested : 1.0]
|
|
122
|
+
class_node['branch-rate'] = '%.16f' % [(coverage_file.num_branches_testable > 0) ? coverage_file.rate_branches_tested : 1.0]
|
|
123
|
+
class_node['complexity'] = '0.0'
|
|
124
|
+
|
|
125
|
+
methods_node = Nokogiri::XML::Node.new "methods", @doc
|
|
126
|
+
methods_node.parent = class_node
|
|
127
|
+
lines_node = Nokogiri::XML::Node.new "lines", @doc
|
|
128
|
+
lines_node.parent = class_node
|
|
129
|
+
|
|
130
|
+
coverage_file.cleaned_gcov_data.split("\n").each do |line|
|
|
131
|
+
line_segments = line.split(':')
|
|
132
|
+
if coverage_file.coverage_for_line(line)
|
|
133
|
+
line_node = create_line_node(line, coverage_file)
|
|
134
|
+
line_node.parent = lines_node
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
class_node
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def create_line_node(line, coverage_file)
|
|
141
|
+
line_number = line.split(':')[1].strip.to_i
|
|
142
|
+
line_node = Nokogiri::XML::Node.new "line", @doc
|
|
143
|
+
line_node['number'] = line_number
|
|
144
|
+
line_node['branch'] = "false"
|
|
145
|
+
line_node['hits'] = coverage_file.coverage_for_line(line)
|
|
146
|
+
|
|
147
|
+
unless coverage_file.branch_coverage_data_for_statement_on_line(line_number).empty?
|
|
148
|
+
line_node['branch'] = "true"
|
|
149
|
+
conditions_node = Nokogiri::XML::Node.new "conditions", @doc
|
|
150
|
+
conditions_node.parent = line_node
|
|
151
|
+
condition_node = Nokogiri::XML::Node.new "condition", @doc
|
|
152
|
+
condition_node.parent = conditions_node
|
|
153
|
+
condition_node['number'] = "0"
|
|
154
|
+
condition_node['type'] = "jump"
|
|
155
|
+
branches_testable = coverage_file.num_branches_for_statement_on_line(line_number)
|
|
156
|
+
branch_hits = coverage_file.num_branch_hits_for_statement_on_line(line_number)
|
|
157
|
+
condition_coverage = coverage_file.percentage_branch_coverage_for_statement_on_line(line_number)
|
|
158
|
+
condition_node['coverage'] = "#{condition_coverage.to_i}%"
|
|
159
|
+
line_node['condition-coverage'] = "#{condition_coverage.to_i}% (#{branch_hits}/#{branches_testable})"
|
|
160
|
+
end
|
|
161
|
+
line_node
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def create_empty_xml_report
|
|
165
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
|
166
|
+
xml.doc.create_internal_subset(
|
|
167
|
+
'coverage',
|
|
168
|
+
nil,
|
|
169
|
+
"http://cobertura.sourceforge.net/xml/coverage-04.dtd"
|
|
170
|
+
)
|
|
171
|
+
xml.coverage do
|
|
172
|
+
xml.sources do
|
|
173
|
+
xml.source
|
|
174
|
+
end
|
|
175
|
+
xml.packages
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
@doc = builder.doc
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
module Slather
|
|
2
|
+
module CoverageService
|
|
3
|
+
module Coveralls
|
|
4
|
+
|
|
5
|
+
def coverage_file_class
|
|
6
|
+
Slather::CoverallsCoverageFile
|
|
7
|
+
end
|
|
8
|
+
private :coverage_file_class
|
|
9
|
+
|
|
10
|
+
def travis_job_id
|
|
11
|
+
ENV['TRAVIS_JOB_ID']
|
|
12
|
+
end
|
|
13
|
+
private :travis_job_id
|
|
14
|
+
|
|
15
|
+
def circleci_job_id
|
|
16
|
+
ENV['CIRCLE_BUILD_NUM']
|
|
17
|
+
end
|
|
18
|
+
private :circleci_job_id
|
|
19
|
+
|
|
20
|
+
def circleci_pull_request
|
|
21
|
+
ENV['CIRCLE_PR_NUMBER'] || ENV['CI_PULL_REQUEST'] || ""
|
|
22
|
+
end
|
|
23
|
+
private :circleci_pull_request
|
|
24
|
+
|
|
25
|
+
def jenkins_job_id
|
|
26
|
+
ENV['BUILD_ID']
|
|
27
|
+
end
|
|
28
|
+
private :jenkins_job_id
|
|
29
|
+
|
|
30
|
+
def jenkins_branch_name
|
|
31
|
+
branch_name = ENV['GIT_BRANCH']
|
|
32
|
+
if branch_name.include? 'origin/'
|
|
33
|
+
branch_name[7...branch_name.length]
|
|
34
|
+
else
|
|
35
|
+
branch_name
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
private :jenkins_branch_name
|
|
39
|
+
|
|
40
|
+
def buildkite_job_id
|
|
41
|
+
ENV['BUILDKITE_BUILD_NUMBER']
|
|
42
|
+
end
|
|
43
|
+
private :buildkite_job_id
|
|
44
|
+
|
|
45
|
+
def buildkite_pull_request
|
|
46
|
+
ENV['BUILDKITE_PULL_REQUEST']
|
|
47
|
+
end
|
|
48
|
+
private :buildkite_pull_request
|
|
49
|
+
|
|
50
|
+
def jenkins_git_info
|
|
51
|
+
{
|
|
52
|
+
head: {
|
|
53
|
+
id: ENV['sha1'],
|
|
54
|
+
author_name: ENV['ghprbActualCommitAuthor'],
|
|
55
|
+
message: ENV['ghprbPullTitle']
|
|
56
|
+
},
|
|
57
|
+
branch: jenkins_branch_name
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
private :jenkins_git_info
|
|
61
|
+
|
|
62
|
+
def circleci_build_url
|
|
63
|
+
"https://circleci.com/gh/" + ENV['CIRCLE_PROJECT_USERNAME'] || "" + "/" + ENV['CIRCLE_PROJECT_REPONAME'] || "" + "/" + ENV['CIRCLE_BUILD_NUM'] || ""
|
|
64
|
+
end
|
|
65
|
+
private :circleci_build_url
|
|
66
|
+
|
|
67
|
+
def circleci_git_info
|
|
68
|
+
{
|
|
69
|
+
:head => {
|
|
70
|
+
:id => (ENV['CIRCLE_SHA1'] || ""),
|
|
71
|
+
:author_name => (ENV['CIRCLE_PR_USERNAME'] || ENV['CIRCLE_USERNAME'] || ""),
|
|
72
|
+
:message => (`git log --format=%s -n 1 HEAD`.chomp || "")
|
|
73
|
+
},
|
|
74
|
+
:branch => (ENV['CIRCLE_BRANCH'] || "")
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
private :circleci_git_info
|
|
78
|
+
|
|
79
|
+
def buildkite_git_info
|
|
80
|
+
{
|
|
81
|
+
:head => {
|
|
82
|
+
:id => ENV['BUILDKITE_COMMIT'],
|
|
83
|
+
:author_name => (`git log --format=%an -n 1 HEAD`.chomp || ""),
|
|
84
|
+
:author_email => (`git log --format=%ae -n 1 HEAD`.chomp || ""),
|
|
85
|
+
:message => (`git log --format=%s -n 1 HEAD`.chomp || "")
|
|
86
|
+
},
|
|
87
|
+
:branch => ENV['BUILDKITE_BRANCH']
|
|
88
|
+
}
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def buildkite_build_url
|
|
92
|
+
"https://buildkite.com/" + ENV['BUILDKITE_PROJECT_SLUG'] + "/builds/" + ENV['BUILDKITE_BUILD_NUMBER'] + "#"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def coveralls_coverage_data
|
|
96
|
+
if ci_service == :travis_ci || ci_service == :travis_pro
|
|
97
|
+
if travis_job_id
|
|
98
|
+
if ci_service == :travis_ci
|
|
99
|
+
{
|
|
100
|
+
:service_job_id => travis_job_id,
|
|
101
|
+
:service_name => "travis-ci",
|
|
102
|
+
:source_files => coverage_files.map(&:as_json)
|
|
103
|
+
}.to_json
|
|
104
|
+
elsif ci_service == :travis_pro
|
|
105
|
+
{
|
|
106
|
+
:service_job_id => travis_job_id,
|
|
107
|
+
:service_name => "travis-pro",
|
|
108
|
+
:repo_token => coverage_access_token,
|
|
109
|
+
:source_files => coverage_files.map(&:as_json)
|
|
110
|
+
}.to_json
|
|
111
|
+
end
|
|
112
|
+
else
|
|
113
|
+
raise StandardError, "Environment variable `TRAVIS_JOB_ID` not set. Is this running on a travis build?"
|
|
114
|
+
end
|
|
115
|
+
elsif ci_service == :circleci
|
|
116
|
+
if circleci_job_id
|
|
117
|
+
coveralls_hash = {
|
|
118
|
+
:service_job_id => circleci_job_id,
|
|
119
|
+
:service_name => "circleci",
|
|
120
|
+
:repo_token => coverage_access_token,
|
|
121
|
+
:source_files => coverage_files.map(&:as_json),
|
|
122
|
+
:git => circleci_git_info,
|
|
123
|
+
:service_build_url => circleci_build_url
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if circleci_pull_request != nil && circleci_pull_request.length > 0
|
|
127
|
+
coveralls_hash[:service_pull_request] = circleci_pull_request.split("/").last
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
coveralls_hash.to_json
|
|
131
|
+
else
|
|
132
|
+
raise StandardError, "Environment variable `CIRCLE_BUILD_NUM` not set. Is this running on a circleci build?"
|
|
133
|
+
end
|
|
134
|
+
elsif ci_service == :jenkins
|
|
135
|
+
if jenkins_job_id
|
|
136
|
+
{
|
|
137
|
+
service_job_id: jenkins_job_id,
|
|
138
|
+
service_name: "jenkins",
|
|
139
|
+
repo_token: coverage_access_token,
|
|
140
|
+
source_files: coverage_files.map(&:as_json),
|
|
141
|
+
git: jenkins_git_info
|
|
142
|
+
}.to_json
|
|
143
|
+
else
|
|
144
|
+
raise StandardError, "Environment variable `BUILD_ID` not set. Is this running on a jenkins build?"
|
|
145
|
+
end
|
|
146
|
+
elsif ci_service == :buildkite
|
|
147
|
+
if buildkite_job_id
|
|
148
|
+
{
|
|
149
|
+
:service_job_id => buildkite_job_id,
|
|
150
|
+
:service_name => "buildkite",
|
|
151
|
+
:repo_token => coverage_access_token,
|
|
152
|
+
:source_files => coverage_files.map(&:as_json),
|
|
153
|
+
:git => buildkite_git_info,
|
|
154
|
+
:service_build_url => buildkite_build_url,
|
|
155
|
+
:service_pull_request => buildkite_pull_request
|
|
156
|
+
}.to_json
|
|
157
|
+
else
|
|
158
|
+
raise StandardError, "Environment variable `BUILDKITE_BUILD_NUMBER` not set. Is this running on a buildkite build?"
|
|
159
|
+
end
|
|
160
|
+
else
|
|
161
|
+
raise StandardError, "No support for ci named #{ci_service}"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
private :coveralls_coverage_data
|
|
165
|
+
|
|
166
|
+
def post
|
|
167
|
+
f = File.open('coveralls_json_file', 'w+')
|
|
168
|
+
begin
|
|
169
|
+
f.write(coveralls_coverage_data)
|
|
170
|
+
f.close
|
|
171
|
+
`curl -s --form json_file=@#{f.path} #{coveralls_api_jobs_path}`
|
|
172
|
+
rescue StandardError => e
|
|
173
|
+
FileUtils.rm(f)
|
|
174
|
+
raise e
|
|
175
|
+
end
|
|
176
|
+
FileUtils.rm(f)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def coveralls_api_jobs_path
|
|
180
|
+
"https://coveralls.io/api/v1/jobs"
|
|
181
|
+
end
|
|
182
|
+
private :coveralls_api_jobs_path
|
|
183
|
+
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|