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.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +45 -0
  4. data/.travis.yml +19 -0
  5. data/CHANGELOG.md +132 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +206 -0
  9. data/Rakefile +1 -0
  10. data/assets/highlight.pack.js +1 -0
  11. data/assets/list.min.js +1 -0
  12. data/assets/slather.css +316 -0
  13. data/bin/slather +117 -0
  14. data/docs/logo.jpg +0 -0
  15. data/lib/cocoapods_plugin.rb +10 -0
  16. data/lib/slather.rb +20 -0
  17. data/lib/slather/coverage_file.rb +195 -0
  18. data/lib/slather/coverage_service/cobertura_xml_output.rb +183 -0
  19. data/lib/slather/coverage_service/coveralls.rb +186 -0
  20. data/lib/slather/coverage_service/gutter_json_output.rb +50 -0
  21. data/lib/slather/coverage_service/hardcover.rb +61 -0
  22. data/lib/slather/coverage_service/html_output.rb +244 -0
  23. data/lib/slather/coverage_service/simple_output.rb +31 -0
  24. data/lib/slather/coveralls_coverage_file.rb +13 -0
  25. data/lib/slather/project.rb +132 -0
  26. data/lib/slather/version.rb +3 -0
  27. data/slather.gemspec +32 -0
  28. data/spec/fixtures/fixtures.xcodeproj/project.pbxproj +496 -0
  29. data/spec/fixtures/fixtures.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  30. data/spec/fixtures/fixtures.xcodeproj/xcshareddata/xcschemes/fixtures.xcscheme +69 -0
  31. data/spec/fixtures/fixtures/Supporting Files/fixtures-Prefix.pch +9 -0
  32. data/spec/fixtures/fixtures/fixtures.h +16 -0
  33. data/spec/fixtures/fixtures/fixtures.m +23 -0
  34. data/spec/fixtures/fixtures/fixtures_cpp.cpp +9 -0
  35. data/spec/fixtures/fixtures/fixtures_cpp.h +6 -0
  36. data/spec/fixtures/fixtures/fixtures_m.h +5 -0
  37. data/spec/fixtures/fixtures/fixtures_m.m +5 -0
  38. data/spec/fixtures/fixtures/fixtures_mm.h +5 -0
  39. data/spec/fixtures/fixtures/fixtures_mm.mm +5 -0
  40. data/spec/fixtures/fixtures/more_files/Branches.h +15 -0
  41. data/spec/fixtures/fixtures/more_files/Branches.m +45 -0
  42. data/spec/fixtures/fixtures/more_files/Empty.h +13 -0
  43. data/spec/fixtures/fixtures/more_files/Empty.m +13 -0
  44. data/spec/fixtures/fixtures/more_files/peekaview.h +13 -0
  45. data/spec/fixtures/fixtures/more_files/peekaview.m +31 -0
  46. data/spec/fixtures/fixturesTests/BranchesTests.m +38 -0
  47. data/spec/fixtures/fixturesTests/Supporting Files/en.lproj/InfoPlist.strings +2 -0
  48. data/spec/fixtures/fixturesTests/Supporting Files/fixturesTests-Info.plist +22 -0
  49. data/spec/fixtures/fixturesTests/fixturesTests.m +36 -0
  50. data/spec/fixtures/fixturesTests/peekaviewTests.m +34 -0
  51. data/spec/fixtures/fixtures_html/Branches.m.html +261 -0
  52. data/spec/fixtures/fixtures_html/BranchesTests.m.html +228 -0
  53. data/spec/fixtures/fixtures_html/Empty.m.html +30 -0
  54. data/spec/fixtures/fixtures_html/fixtures.m.html +151 -0
  55. data/spec/fixtures/fixtures_html/fixturesTests.m.html +216 -0
  56. data/spec/fixtures/fixtures_html/fixtures_cpp.cpp.html +30 -0
  57. data/spec/fixtures/fixtures_html/fixtures_m.m.html +30 -0
  58. data/spec/fixtures/fixtures_html/fixtures_mm.mm.html +30 -0
  59. data/spec/fixtures/fixtures_html/index.html +134 -0
  60. data/spec/fixtures/fixtures_html/peekaview.m.html +190 -0
  61. data/spec/fixtures/fixtures_html/peekaviewTests.m.html +206 -0
  62. data/spec/fixtures/gutter.json +1 -0
  63. data/spec/slather/cocoapods_plugin_spec.rb +21 -0
  64. data/spec/slather/coverage_file_spec.rb +337 -0
  65. data/spec/slather/coverage_service/cobertura_xml_spec.rb +49 -0
  66. data/spec/slather/coverage_service/coveralls_spec.rb +122 -0
  67. data/spec/slather/coverage_service/gutter_json_spec.rb +28 -0
  68. data/spec/slather/coverage_service/hardcover_spec.rb +87 -0
  69. data/spec/slather/coverage_service/html_output_spec.rb +179 -0
  70. data/spec/slather/coverage_service/simple_output_spec.rb +35 -0
  71. data/spec/slather/fixtures.gcno +0 -0
  72. data/spec/slather/project_spec.rb +288 -0
  73. data/spec/spec_helper.rb +27 -0
  74. metadata +319 -0
@@ -0,0 +1,50 @@
1
+ module Slather
2
+ module CoverageService
3
+ module GutterJsonOutput
4
+
5
+ def coverage_file_class
6
+ Slather::CoverageFile
7
+ end
8
+ private :coverage_file_class
9
+
10
+ def post
11
+ output = { 'meta' => { 'timestamp' => DateTime.now.strftime('%Y-%m-%d %H:%M:%S.%6N') } }
12
+ symbols = {}
13
+
14
+ coverage_files.each do |coverage_file|
15
+ next unless coverage_file.gcov_data
16
+
17
+ filename = coverage_file.source_file_pathname.to_s
18
+ filename = filename.sub(Pathname.pwd.to_s, '').reverse.chomp("/").reverse
19
+
20
+ coverage_file.cleaned_gcov_data.split("\n").each do |line|
21
+ data = line.split(':')
22
+
23
+ line_number = data[1].to_i
24
+ next unless line_number > 0
25
+
26
+ coverage = data[0].strip
27
+
28
+ symbol = { 'line' => line_number,
29
+ 'long_text' => '',
30
+ 'short_text' => coverage }
31
+
32
+ if coverage != '-'
33
+ symbol['background_color'] = coverage.to_i > 0 ? '0x35CC4B' : '0xFC635E'
34
+ end
35
+
36
+ if symbols.has_key?(filename)
37
+ symbols[filename] << symbol
38
+ else
39
+ symbols[filename] = [ symbol ]
40
+ end
41
+ end
42
+ end
43
+
44
+ output['symbols_by_file'] = symbols
45
+ File.open('.gutter.json', 'w') { |file| file.write(output.to_json) }
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,61 @@
1
+ module Slather
2
+ module CoverageService
3
+ module Hardcover
4
+
5
+ def coverage_file_class
6
+ Slather::CoverallsCoverageFile
7
+ end
8
+ private :coverage_file_class
9
+
10
+ def jenkins_job_id
11
+ "#{ENV['JOB_NAME']}/#{ENV['BUILD_NUMBER']}"
12
+ end
13
+ private :jenkins_job_id
14
+
15
+ def hardcover_coverage_data
16
+ if ci_service == :jenkins_ci
17
+ if jenkins_job_id
18
+ {
19
+ :service_job_id => jenkins_job_id,
20
+ :service_name => "jenkins-ci",
21
+ :repo_token => Project.yml["hardcover_repo_token"],
22
+ :source_files => coverage_files.map(&:as_json)
23
+ }.to_json
24
+ else
25
+ raise StandardError, "Environment variables `BUILD_NUMBER` and `JOB_NAME` are not set. Is this running on a Jenkins build?"
26
+ end
27
+ else
28
+ raise StandardError, "No support for ci named #{ci_service}"
29
+ end
30
+ end
31
+ private :hardcover_coverage_data
32
+
33
+ def post
34
+ f = File.open('hardcover_json_file', 'w+')
35
+ begin
36
+ f.write(hardcover_coverage_data)
37
+ f.close
38
+ `curl --form json_file=@#{f.path} #{hardcover_api_jobs_path}`
39
+ rescue StandardError => e
40
+ FileUtils.rm(f)
41
+ raise e
42
+ end
43
+ FileUtils.rm(f)
44
+ end
45
+
46
+ def hardcover_api_jobs_path
47
+ "#{hardcover_base_url}/v1/jobs"
48
+ end
49
+ private :hardcover_api_jobs_path
50
+
51
+ def hardcover_base_url
52
+ url = Project.yml["hardcover_base_url"]
53
+ unless url
54
+ raise "No `hardcover_base_url` configured. Please add it to your `.slather.yml`"
55
+ end
56
+ url
57
+ end
58
+ private :hardcover_base_url
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,244 @@
1
+ require 'nokogiri'
2
+
3
+ module Slather
4
+ module CoverageService
5
+ module HtmlOutput
6
+
7
+ def coverage_file_class
8
+ Slather::CoverageFile
9
+ end
10
+ private :coverage_file_class
11
+
12
+ def directory_path
13
+ is_path_valid = !output_directory.nil? && !output_directory.strip.eql?("")
14
+ is_path_valid ? File.expand_path(output_directory) : "html"
15
+ end
16
+ private :directory_path
17
+
18
+ def post
19
+ create_html_reports(coverage_files)
20
+ generate_reports(@docs)
21
+
22
+ index_html_path = File.join(directory_path, "index.html")
23
+ if show_html
24
+ open_coverage index_html_path
25
+ else
26
+ print_path_coverage index_html_path
27
+ end
28
+ end
29
+
30
+ def print_path_coverage(index_html)
31
+ path = File.expand_path index_html
32
+ puts "\nTo open the html reports, use \n\nopen '#{path}'\n\nor use '--show' flag to open it automatically.\n\n"
33
+ end
34
+
35
+ def open_coverage(index_html)
36
+ path = File.expand_path index_html
37
+ `open '#{path}'` if File.exist?(path)
38
+ end
39
+
40
+ def create_html_reports(coverage_files)
41
+ create_index_html(coverage_files)
42
+ create_htmls_from_files(coverage_files)
43
+ end
44
+
45
+ def generate_reports(reports)
46
+ FileUtils.rm_rf(directory_path) if Dir.exist?(directory_path)
47
+ FileUtils.mkdir_p(directory_path)
48
+
49
+ reports.each do |name, doc|
50
+ html_file = File.join(directory_path, "#{name}.html")
51
+ File.write(html_file, doc.to_html)
52
+ end
53
+ end
54
+
55
+ def create_index_html(coverage_files)
56
+ project_name = File.basename(self.xcodeproj)
57
+ template = generate_html_template(project_name, true, false)
58
+
59
+ total_relevant_lines = 0
60
+ total_tested_lines = 0
61
+ coverage_files.each { |coverage_file|
62
+ total_tested_lines += coverage_file.num_lines_tested
63
+ total_relevant_lines += coverage_file.num_lines_testable
64
+ }
65
+
66
+ builder = Nokogiri::HTML::Builder.with(template.at('#reports')) { |cov|
67
+ cov.h2 "Files for \"#{project_name}\""
68
+
69
+ cov.h4 {
70
+ percentage = (total_tested_lines / total_relevant_lines.to_f) * 100.0
71
+ cov.span "Total Coverage : "
72
+ cov.span '%.2f%%' % percentage, :class => class_for_coverage_percentage(percentage), :id => "total_coverage"
73
+ }
74
+
75
+ cov.input(:class => "search", :placeholder => "Search")
76
+
77
+ cov.table(:class => "coverage_list", :cellspacing => 0, :cellpadding => 0) {
78
+
79
+ cov.thead {
80
+ cov.tr {
81
+ cov.th "%", :class => "col_num sort", "data-sort" => "data_percentage"
82
+ cov.th "File", :class => "sort", "data-sort" => "data_filename"
83
+ cov.th "Lines", :class => "col_percent sort", "data-sort" => "data_lines"
84
+ cov.th "Relevant", :class => "col_percent sort", "data-sort" => "data_relevant"
85
+ cov.th "Covered", :class => "col_percent sort", "data-sort" => "data_covered"
86
+ cov.th "Missed", :class => "col_percent sort", "data-sort" => "data_missed"
87
+ }
88
+ }
89
+
90
+ cov.tbody(:class => "list") {
91
+ coverage_files.each { |coverage_file|
92
+ filename = File.basename(coverage_file.source_file_pathname_relative_to_repo_root)
93
+ filename_link = "#{filename}.html"
94
+
95
+ cov.tr {
96
+ percentage = coverage_file.percentage_lines_tested
97
+
98
+ cov.td { cov.span '%.2f' % percentage, :class => "percentage #{class_for_coverage_percentage(percentage)} data_percentage" }
99
+ cov.td(:class => "data_filename") {
100
+ cov.a filename, :href => filename_link
101
+ }
102
+ cov.td "#{coverage_file.line_coverage_data.count}", :class => "data_lines"
103
+ cov.td "#{coverage_file.num_lines_testable}", :class => "data_relevant"
104
+ cov.td "#{coverage_file.num_lines_tested}", :class => "data_covered"
105
+ cov.td "#{(coverage_file.num_lines_testable - coverage_file.num_lines_tested)}", :class => "data_missed"
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ @docs = Hash.new
113
+ @docs[:index] = builder.doc
114
+ end
115
+
116
+ def create_htmls_from_files(coverage_files)
117
+ coverage_files.map { |file| create_html_from_file file }
118
+ end
119
+
120
+ def create_html_from_file(coverage_file)
121
+ filepath = coverage_file.source_file_pathname_relative_to_repo_root
122
+ filename = File.basename(filepath)
123
+ percentage = coverage_file.percentage_lines_tested
124
+
125
+ cleaned_gcov_lines = coverage_file.cleaned_gcov_data.split("\n")
126
+ is_file_empty = (cleaned_gcov_lines.count <= 0)
127
+
128
+ template = generate_html_template(filename, false, is_file_empty)
129
+
130
+ builder = Nokogiri::HTML::Builder.with(template.at('#reports')) { |cov|
131
+ cov.h2(:class => "cov_title") {
132
+ cov.span("Coverage for \"#{filename}\"" + (!is_file_empty ? " : " : ""))
133
+ cov.span("#{'%.2f' % percentage}%", :class => class_for_coverage_percentage(percentage)) unless is_file_empty
134
+ }
135
+
136
+ cov.h4("(#{coverage_file.num_lines_tested} of #{coverage_file.num_lines_testable} relevant lines covered)", :class => "cov_subtitle")
137
+ cov.h4(filepath, :class => "cov_filepath")
138
+
139
+ if is_file_empty
140
+ cov.p "¯\\_(ツ)_/¯"
141
+ next
142
+ end
143
+
144
+ cov.table(:class => "source_code") {
145
+ cleaned_gcov_lines.each do |line|
146
+ data = line.split(':', 3)
147
+
148
+ line_number = data[1].to_i
149
+ next unless line_number > 0
150
+
151
+ coverage_data = data[0].strip
152
+ line_data = [line_number, data[2], hits_for_coverage_data(coverage_data)]
153
+ classes = ["num", "src", "coverage"]
154
+
155
+ cov.tr(:class => class_for_coverage_data(coverage_data)) {
156
+ line_data.each_with_index { |line, idx|
157
+ if idx != 1
158
+ cov.td(line, :class => classes[idx])
159
+ else
160
+ cov.td(:class => classes[idx]) {
161
+ cov.pre { cov.code(line, :class => "objc") }
162
+ }
163
+ end
164
+ }
165
+ }
166
+ end
167
+ }
168
+ }
169
+
170
+ @docs[filename] = builder.doc
171
+ end
172
+
173
+ def generate_html_template(title, is_index, is_file_empty)
174
+ logo_path = File.join(gem_root_path, "docs/logo.jpg")
175
+ css_path = File.join(gem_root_path, "assets/slather.css")
176
+ highlight_js_path = File.join(gem_root_path, "assets/highlight.pack.js")
177
+ list_js_path = File.join(gem_root_path, "assets/list.min.js")
178
+
179
+ builder = Nokogiri::HTML::Builder.new do |doc|
180
+ doc.html {
181
+ doc.head {
182
+ doc.title "#{title} - Slather"
183
+ doc.link :href => css_path, :media => "all", :rel => "stylesheet"
184
+ }
185
+ doc.body {
186
+ doc.header {
187
+ doc.div(:class => "row") {
188
+ doc.a(:href => "index.html") { doc.img(:src => logo_path, :alt => "Slather logo") }
189
+ }
190
+ }
191
+ doc.div(:class => "row") { doc.div(:id => "reports") }
192
+ doc.footer {
193
+ doc.div(:class => "row") {
194
+ doc.p { doc.a("Fork me on Github", :href => "https://github.com/venmo/slather") }
195
+ doc.p("© #{Date.today.year} Slather")
196
+ }
197
+ }
198
+
199
+ if is_index
200
+ doc.script :src => list_js_path
201
+ doc.script "var reports = new List('reports', { valueNames: [ 'data_percentage', 'data_filename', 'data_lines', 'data_relevant', 'data_covered', 'data_missed' ]});"
202
+ else
203
+ unless is_file_empty
204
+ doc.script :src => highlight_js_path
205
+ doc.script "hljs.initHighlightingOnLoad();"
206
+ end
207
+ end
208
+ }
209
+ }
210
+ end
211
+ builder.doc
212
+ end
213
+
214
+ def gem_root_path
215
+ File.expand_path File.join(File.dirname(__dir__), "../..")
216
+ end
217
+
218
+ def class_for_coverage_data(coverage_data)
219
+ case coverage_data
220
+ when /\d/ then "covered"
221
+ when /#/ then "missed"
222
+ else "never"
223
+ end
224
+ end
225
+
226
+ def hits_for_coverage_data(coverage_data)
227
+ case coverage_data
228
+ when /\d/ then (coverage_data.to_i > 0) ? "#{coverage_data}x" : ""
229
+ when /#/ then "!"
230
+ else ""
231
+ end
232
+ end
233
+
234
+ def class_for_coverage_percentage(percentage)
235
+ case
236
+ when percentage > 85 then "cov_high"
237
+ when percentage > 70 then "cov_medium"
238
+ else "cov_low"
239
+ end
240
+ end
241
+
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,31 @@
1
+ module Slather
2
+ module CoverageService
3
+ module SimpleOutput
4
+
5
+ def coverage_file_class
6
+ Slather::CoverageFile
7
+ end
8
+ private :coverage_file_class
9
+
10
+ def post
11
+ total_project_lines = 0
12
+ total_project_lines_tested = 0
13
+ coverage_files.each do |coverage_file|
14
+ # ignore lines that don't count towards coverage (comments, whitespace, etc). These are nil in the array.
15
+
16
+ lines_tested = coverage_file.num_lines_tested
17
+ total_lines = coverage_file.num_lines_testable
18
+ percentage = '%.2f' % [coverage_file.percentage_lines_tested]
19
+
20
+ total_project_lines_tested += lines_tested
21
+ total_project_lines += total_lines
22
+
23
+ puts "#{coverage_file.source_file_pathname_relative_to_repo_root}: #{lines_tested} of #{total_lines} lines (#{percentage}%)"
24
+ end
25
+ total_percentage = '%.2f' % [(total_project_lines_tested / total_project_lines.to_f) * 100.0]
26
+ puts "Test Coverage: #{total_percentage}%"
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ module Slather
2
+ class CoverallsCoverageFile < CoverageFile
3
+
4
+ def as_json
5
+ {
6
+ :name => source_file_pathname_relative_to_repo_root.to_s,
7
+ :source => source_data,
8
+ :coverage => line_coverage_data
9
+ }
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,132 @@
1
+ require 'fileutils'
2
+ require 'xcodeproj'
3
+ require 'json'
4
+ require 'yaml'
5
+
6
+ module Xcodeproj
7
+ class Project
8
+
9
+ def slather_setup_for_coverage
10
+ build_configurations.each do |build_configuration|
11
+ build_configuration.build_settings["GCC_INSTRUMENT_PROGRAM_FLOW_ARCS"] = "YES"
12
+ build_configuration.build_settings["GCC_GENERATE_TEST_COVERAGE_FILES"] = "YES"
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+
19
+ module Slather
20
+ class Project < Xcodeproj::Project
21
+
22
+ attr_accessor :build_directory, :ignore_list, :ci_service, :coverage_service, :coverage_access_token, :source_directory, :output_directory, :xcodeproj, :show_html
23
+
24
+ alias_method :setup_for_coverage, :slather_setup_for_coverage
25
+
26
+ def self.open(xcodeproj)
27
+ proj = super
28
+ proj.configure_from_yml
29
+ proj.xcodeproj = xcodeproj
30
+ proj
31
+ end
32
+
33
+ def derived_data_dir
34
+ File.expand_path('~') + "/Library/Developer/Xcode/DerivedData/"
35
+ end
36
+ private :derived_data_dir
37
+
38
+ def build_directory
39
+ @build_directory || derived_data_dir
40
+ end
41
+
42
+ def coverage_files
43
+ coverage_files = Dir["#{build_directory}/**/*.gcno"].map do |file|
44
+ coverage_file = coverage_file_class.new(self, file)
45
+ # If there's no source file for this gcno, it probably belongs to another project.
46
+ coverage_file.source_file_pathname && !coverage_file.ignored? ? coverage_file : nil
47
+ end.compact
48
+
49
+ if coverage_files.empty?
50
+ raise StandardError, "No coverage files found. Are you sure your project is setup for generating coverage files? Try `slather setup your/project.xcodeproj`"
51
+ else
52
+ dedupe(coverage_files)
53
+ end
54
+ end
55
+ private :coverage_files
56
+
57
+ def dedupe(coverage_files)
58
+ coverage_files.group_by(&:source_file_pathname).values.map { |cf_array| cf_array.max_by(&:percentage_lines_tested) }
59
+ end
60
+ private :dedupe
61
+
62
+ def self.yml_filename
63
+ '.slather.yml'
64
+ end
65
+
66
+ def self.yml
67
+ @yml ||= File.exist?(yml_filename) ? YAML.load_file(yml_filename) : {}
68
+ end
69
+
70
+ def configure_from_yml
71
+ configure_build_directory_from_yml
72
+ configure_ignore_list_from_yml
73
+ configure_ci_service_from_yml
74
+ configure_coverage_access_token_from_yml
75
+ configure_coverage_service_from_yml
76
+ configure_source_directory_from_yml
77
+ configure_output_directory_from_yml
78
+ end
79
+
80
+ def configure_build_directory_from_yml
81
+ self.build_directory = self.class.yml["build_directory"] if self.class.yml["build_directory"] && !@build_directory
82
+ end
83
+
84
+ def configure_source_directory_from_yml
85
+ self.source_directory ||= self.class.yml["source_directory"] if self.class.yml["source_directory"]
86
+ end
87
+
88
+ def configure_output_directory_from_yml
89
+ self.output_directory ||= self.class.yml["output_directory"] if self.class.yml["output_directory"]
90
+ end
91
+
92
+ def configure_ignore_list_from_yml
93
+ self.ignore_list ||= [(self.class.yml["ignore"] || [])].flatten
94
+ end
95
+
96
+ def configure_ci_service_from_yml
97
+ self.ci_service ||= (self.class.yml["ci_service"] || :travis_ci)
98
+ end
99
+
100
+ def ci_service=(service)
101
+ @ci_service = service && service.to_sym
102
+ end
103
+
104
+ def configure_coverage_service_from_yml
105
+ self.coverage_service ||= (self.class.yml["coverage_service"] || :terminal)
106
+ end
107
+
108
+ def configure_coverage_access_token_from_yml
109
+ self.coverage_access_token ||= (ENV["COVERAGE_ACCESS_TOKEN"] || self.class.yml["coverage_access_token"] || "")
110
+ end
111
+
112
+ def coverage_service=(service)
113
+ service = service && service.to_sym
114
+ if service == :coveralls
115
+ extend(Slather::CoverageService::Coveralls)
116
+ elsif service == :hardcover
117
+ extend(Slather::CoverageService::Hardcover)
118
+ elsif service == :terminal
119
+ extend(Slather::CoverageService::SimpleOutput)
120
+ elsif service == :gutter_json
121
+ extend(Slather::CoverageService::GutterJsonOutput)
122
+ elsif service == :cobertura_xml
123
+ extend(Slather::CoverageService::CoberturaXmlOutput)
124
+ elsif service == :html
125
+ extend(Slather::CoverageService::HtmlOutput)
126
+ else
127
+ raise ArgumentError, "`#{coverage_service}` is not a valid coverage service. Try `terminal`, `coveralls`, `gutter_json`, `cobertura_xml` or `html`"
128
+ end
129
+ @coverage_service = service
130
+ end
131
+ end
132
+ end