slather 2.4.9 → 2.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e8bb7fa0d1346a2a81a47272a1f55d2625a8457d8c4b88a82f2b737792aa39a
4
- data.tar.gz: a375044a21453e30695adf31c20f5a9ea885091d1eb3ac6fafc3f3ef9d44d752
3
+ metadata.gz: 82acbc5ecfb8610c49f39cdea2ff67387d30b3c41bf78c34095a59c7b572e802
4
+ data.tar.gz: a1c830dddd4d52d8641665086ad6a63c294f00a7dd3370c7cd588c8c586db06e
5
5
  SHA512:
6
- metadata.gz: 9a9dddfd5dcf8160cd31327f170030f3520c2a885b7c56f6f2d7d470e5442b8c49c3f30ba289664767cc43d597415c2309654699a5ac2fd59db90ac5bc796697
7
- data.tar.gz: 5f67d766eb09e18df9a2bf1016f49a3a7a7f912be2bf5e05ed31c925540fb51ccaef34e53f1dffaed6aadfef827a3e69aa1c7144fa30a024f9f72a1ce2d47605
6
+ metadata.gz: 239d29e6e221b6119be23af3dc92a7f28d4608dd71acf3f7ed03530953878ab42942903ed3f505a81051c3aa6903ba2a167b37867274b24ab7fc948b9006f08c
7
+ data.tar.gz: c918c164b1da93699be34bef38291b3c43d3d43eb1d258919dc3de0bd84beb855a44318a8d6638d567543ddad7a2e647ca07a1fe12d14678b9c54bbde25a749a
data/.travis.yml CHANGED
@@ -1,10 +1,12 @@
1
1
  language: objective-c
2
2
  script: bundle exec rake
3
- osx_image: xcode9.2
3
+ osx_image: xcode12.2
4
+
5
+ cache: bundler
4
6
 
5
7
  before_install:
6
8
  - curl http://curl.haxx.se/ca/cacert.pem -o /usr/local/share/cacert.pem
7
- - gem install bundler -v "~> 1.0" --no-ri --no-rdoc
9
+ - gem install bundler -v "~> 2.0" --no-document
8
10
 
9
11
  install:
10
12
  - bundle install --without=documentation
data/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v2.7.1
4
+
5
+ * Support generating coverage for framework targets
6
+ [onato](https://github.com/onato)
7
+ [#482](https://github.com/SlatherOrg/slather/pull/482)
8
+
9
+ * Show number of lines in HTML report
10
+ [SiemianHS](https://github.com/SiemianHS)
11
+ [#494](https://github.com/SlatherOrg/slather/pull/494)
12
+
13
+ * Fixed issues with HTML report generation
14
+ [fchiba](https://github.com/fchiba)
15
+ [#483](https://github.com/SlatherOrg/slather/pull/483)
16
+ [#484](https://github.com/SlatherOrg/slather/pull/484)
17
+
18
+ * Don't fail if a source file doesn't exist
19
+ [chillpop](https://github.com/chillpop)
20
+ [#492](https://github.com/SlatherOrg/slather/pull/492)
21
+
22
+ ## v2.7.0
23
+
24
+ * Add Branch Coverage data for ProfData coverage files
25
+ [hborawski](https://github.com/hborawski)
26
+ [#477](https://github.com/SlatherOrg/slather/pull/477)
27
+
28
+ * Fixed 'Argument list too long' when running 'xcrun llvm-cov'
29
+ [samuelsainz](https://github.com/samuelsainz)
30
+ [#476](https://github.com/SlatherOrg/slather/pull/476)
31
+
32
+ ## v2.6.1
33
+
34
+ * Update nokogiri to 1.11
35
+ [ashin-omg](https://github.com/ashin-omg)
36
+ [#473](https://github.com/SlatherOrg/slather/pull/473)
37
+
38
+ ## v2.6.0
39
+
40
+ * Added GitHub actions support
41
+ [martin-key](https://github.com/martin-key), [troyfontaine](https://github.com/troyfontaine)
42
+ [#468](https://github.com/SlatherOrg/slather/pull/468)
43
+
44
+ ## v2.5.0
45
+
46
+ * Fixed activesupport and cocoapods dependencies
47
+ [daneov](https://github.com/daneov)
48
+ [#456](https://github.com/SlatherOrg/slather/pull/467)
49
+
50
+ * Fixed typo in documentation
51
+ [descorp](https://github.com/descorp)
52
+ [#456](https://github.com/SlatherOrg/slather/pull/463)
53
+
3
54
  ## v2.4.9
4
55
 
5
56
  * Added support for Sonarqube output
data/README.md CHANGED
@@ -149,7 +149,7 @@ ignore:
149
149
  - ProjectTestsGroup/*
150
150
  ```
151
151
 
152
- And then in your `.travis.yml` or `circle.yml`, call `slather` after a successful build:
152
+ And then in your `.travis.yml` or `circle.yml` or `github-action.yml`, call `slather` after a successful build:
153
153
 
154
154
  ```yml
155
155
  # .travis.yml
@@ -168,6 +168,25 @@ test:
168
168
 
169
169
  ```
170
170
 
171
+ ```yml
172
+ # github-action.yml
173
+ myjob:
174
+ steps:
175
+ - run: |
176
+ bundle config path vendor/bundle
177
+ bundle install --without=documentation --jobs 4 --retry 3
178
+ - name: Extract branch name
179
+ shell: bash
180
+ run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
181
+ id: get_branch
182
+ - run: bundle exec slather
183
+ env:
184
+ GIT_BRANCH: ${{ steps.get_branch.outputs.branch }}
185
+ CI_PULL_REQUEST: ${{ github.event.number }}
186
+ COVERAGE_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
187
+
188
+ ```
189
+
171
190
  #### Usage with Travis CI Pro
172
191
 
173
192
  To use Coveralls with Travis CI Pro (for private repos), add following lines along with other settings to `.slather.yml`:
@@ -265,3 +284,4 @@ Please make sure to follow our general coding style and add test coverage for ne
265
284
  * [@jhersh](https://github.com/jhersh), CircleCI support.
266
285
  * [@tarbrain](https://github.com/tarbrain), Cobertura support and bugfixing.
267
286
  * [@ikhsan](https://github.com/ikhsan), html support.
287
+ * [@martin-key](https://github.com/martin-key) and [@troyfontaine](https://github.com/troyfontaine), Github Actions support.
data/assets/slather.css CHANGED
@@ -74,6 +74,7 @@ table.source_code {
74
74
  table.source_code td {
75
75
  padding-bottom: 0.3em;
76
76
  }
77
+ code.missed { background-color: rgba(255, 73, 76, 0.3); }
77
78
  table.source_code tr.missed td { background-color: rgba(248, 103, 105, 0.2); }
78
79
  table.source_code tr.covered td { background-color: rgba(103, 207, 124, 0.2); }
79
80
  table.source_code td.num {
@@ -124,7 +125,7 @@ footer p, footer a {
124
125
  Syntax Highlighting using highlight.js (https://highlightjs.org)
125
126
  ------------------------------------------------------------- */
126
127
  .hljs {
127
- display: block;
128
+ display: inline-block;
128
129
  overflow-x: auto;
129
130
  -webkit-text-size-adjust: none;
130
131
  }
@@ -8,12 +8,13 @@ class CoverageCommand < Clamp::Command
8
8
  option ["--jenkins"], :flag, "Indicate that the builds are running on Jenkins"
9
9
  option ["--buildkite"], :flag, "Indicate that the builds are running on Buildkite"
10
10
  option ["--teamcity"], :flag, "Indicate that the builds are running on TeamCity"
11
+ option ["--github"], :flag, "Indicate that the builds are running on Github Actions"
11
12
 
12
13
  option ["--coveralls", "-c"], :flag, "Post coverage results to coveralls"
13
14
  option ["--simple-output", "-s"], :flag, "Output coverage results to the terminal"
14
15
  option ["--gutter-json", "-g"], :flag, "Output coverage results as Gutter JSON format"
15
16
  option ["--cobertura-xml", "-x"], :flag, "Output coverage results as Cobertura XML format"
16
- option ["--sonarqube-xml", "-sq"], :flag, "Output coverage results as Cobertura XML format"
17
+ option ["--sonarqube-xml", "-sq"], :flag, "Output coverage results as SonarQube XML format"
17
18
  option ["--llvm-cov", "-r"], :flag, "Output coverage as llvm-cov format"
18
19
  option ["--json"], :flag, "Output coverage results as simple JSON"
19
20
  option ["--html"], :flag, "Output coverage results as static html pages"
@@ -91,6 +92,8 @@ class CoverageCommand < Clamp::Command
91
92
  project.ci_service = :buildkite
92
93
  elsif teamcity?
93
94
  project.ci_service = :teamcity
95
+ elsif github?
96
+ project.ci_service = :github
94
97
  end
95
98
  end
96
99
 
@@ -43,18 +43,23 @@ module Slather
43
43
 
44
44
  def gcov_data
45
45
  @gcov_data ||= begin
46
- gcov_output = `gcov "#{source_file_pathname}" --object-directory "#{gcno_file_pathname.parent}" --branch-probabilities --branch-counts`
47
- # Sometimes gcov makes gcov files for Cocoa Touch classes, like NSRange. Ignore and delete later.
48
- gcov_files_created = gcov_output.scan(/creating '(.+\..+\.gcov)'/)
46
+ gcov_data = ""
47
+
48
+ Dir.chdir(project.project_dir) do
49
+ gcov_output = `gcov "#{source_file_pathname}" --object-directory "#{gcno_file_pathname.parent}" --branch-probabilities --branch-counts`
50
+ # Sometimes gcov makes gcov files for Cocoa Touch classes, like NSRange. Ignore and delete later.
51
+ gcov_files_created = gcov_output.scan(/creating '(.+\..+\.gcov)'/)
52
+
53
+ gcov_file_name = "./#{source_file_pathname.basename}.gcov"
54
+ if File.exists?(gcov_file_name)
55
+ gcov_data = File.new(gcov_file_name).read
56
+ else
57
+ gcov_data = ""
58
+ end
49
59
 
50
- gcov_file_name = "./#{source_file_pathname.basename}.gcov"
51
- if File.exists?(gcov_file_name)
52
- gcov_data = File.new(gcov_file_name).read
53
- else
54
- gcov_data = ""
60
+ gcov_files_created.each { |file| FileUtils.rm_f(file) }
55
61
  end
56
62
 
57
- gcov_files_created.each { |file| FileUtils.rm_f(file) }
58
63
  gcov_data
59
64
  end
60
65
  end
@@ -36,6 +36,21 @@ module Slather
36
36
  end
37
37
  private :jenkins_job_id
38
38
 
39
+ def github_job_id
40
+ ENV['GITHUB_RUN_ID']
41
+ end
42
+ private :github_job_id
43
+
44
+ def github_pull_request
45
+ ENV['CI_PULL_REQUEST'] || ""
46
+ end
47
+ private :github_pull_request
48
+
49
+ def github_repo_name
50
+ ENV['GITHUB_REPOSITORY'] || ""
51
+ end
52
+ private :github_repo_name
53
+
39
54
  def jenkins_branch_name
40
55
  branch_name = ENV['GIT_BRANCH'] || ENV['BRANCH_NAME']
41
56
  if branch_name.include? 'origin/'
@@ -51,6 +66,11 @@ module Slather
51
66
  end
52
67
  private :teamcity_branch_name
53
68
 
69
+ def github_branch_name
70
+ ENV['GIT_BRANCH'] || `git ls-remote --heads origin | grep $(git rev-parse HEAD) | cut -d / -f 3-`.chomp
71
+ end
72
+ private :github_branch_name
73
+
54
74
  def buildkite_job_id
55
75
  ENV['BUILDKITE_BUILD_NUMBER']
56
76
  end
@@ -119,6 +139,23 @@ module Slather
119
139
  "https://buildkite.com/" + ENV['BUILDKITE_PROJECT_SLUG'] + "/builds/" + ENV['BUILDKITE_BUILD_NUMBER'] + "#"
120
140
  end
121
141
 
142
+ def github_git_info
143
+ {
144
+ :head => {
145
+ :id => ENV['GITHUB_SHA'],
146
+ :author_name => ENV['GITHUB_ACTOR'],
147
+ :message => (`git log --format=%s -n 1 HEAD`.chomp || "")
148
+ },
149
+ :branch => github_branch_name
150
+ }
151
+ end
152
+ private :github_git_info
153
+
154
+ def github_build_url
155
+ "https://github.com/" + ENV['GITHUB_REPOSITORY'] + "/actions/runs/" + ENV['GITHUB_RUN_ID']
156
+ end
157
+ private :github_build_url
158
+
122
159
  def coveralls_coverage_data
123
160
  if ci_service == :travis_ci || ci_service == :travis_pro
124
161
  if travis_job_id
@@ -206,6 +243,26 @@ module Slather
206
243
  else
207
244
  raise StandardError, "Environment variable `TC_BUILD_NUMBER` not set. Is this running on a teamcity build?"
208
245
  end
246
+ elsif ci_service == :github
247
+
248
+ if coverage_access_token.to_s.strip.length == 0
249
+ raise StandardError, "Access token is not set. Uploading coverage data for private repositories requires an access token."
250
+ end
251
+
252
+ if github_job_id
253
+ {
254
+ :service_job_id => github_job_id,
255
+ :service_name => "github",
256
+ :repo_token => coverage_access_token,
257
+ :repo_name => github_repo_name,
258
+ :source_files => coverage_files.map(&:as_json),
259
+ :service_build_url => github_build_url,
260
+ :service_pull_request => github_pull_request,
261
+ :git => github_git_info
262
+ }.to_json
263
+ else
264
+ raise StandardError, "Environment variable `GITHUB_RUN_ID` not set. Is this running on github build?"
265
+ end
209
266
  else
210
267
  raise StandardError, "No support for ci named #{ci_service}"
211
268
  end
@@ -70,9 +70,14 @@ module Slather
70
70
 
71
71
  total_relevant_lines = 0
72
72
  total_tested_lines = 0
73
+ total_relevant_branches = 0
74
+ total_branches_tested = 0
73
75
  coverage_files.each { |coverage_file|
74
76
  total_tested_lines += coverage_file.num_lines_tested
75
77
  total_relevant_lines += coverage_file.num_lines_testable
78
+
79
+ total_relevant_branches += coverage_file.num_branches_testable
80
+ total_branches_tested += coverage_file.num_branches_tested
76
81
  }
77
82
 
78
83
  builder = Nokogiri::HTML::Builder.with(template.at('#reports')) { |cov|
@@ -82,6 +87,22 @@ module Slather
82
87
  percentage = (total_tested_lines / total_relevant_lines.to_f) * 100.0
83
88
  cov.span "Total Coverage : "
84
89
  cov.span decimal_f(percentage) + '%', :class => class_for_coverage_percentage(percentage), :id => "total_coverage"
90
+ cov.span " ("
91
+ cov.span total_tested_lines, :id => "total_tested_lines"
92
+ cov.span " of "
93
+ cov.span total_relevant_lines, :id => "total_relevant_lines"
94
+ cov.span " lines)"
95
+ }
96
+
97
+ cov.h4 {
98
+ percentage = (total_branches_tested / total_relevant_branches.to_f) * 100.0
99
+ cov.span "Total Branch Coverage : "
100
+ cov.span decimal_f(percentage) + '%', :class => class_for_coverage_percentage(percentage), :id => "total_coverage"
101
+ cov.span " ("
102
+ cov.span total_branches_tested, :id => "total_branches_tested"
103
+ cov.span " of "
104
+ cov.span total_relevant_branches, :id => "total_relevant_branches"
105
+ cov.span " lines)"
85
106
  }
86
107
 
87
108
  cov.input(:class => "search", :placeholder => "Search")
@@ -133,6 +154,7 @@ module Slather
133
154
  filepath = coverage_file.source_file_pathname_relative_to_repo_root
134
155
  filename = File.basename(filepath)
135
156
  percentage = coverage_file.percentage_lines_tested
157
+ branch_percentage = coverage_file.rate_branches_tested * 100
136
158
 
137
159
  cleaned_gcov_lines = coverage_file.cleaned_gcov_data.split("\n")
138
160
  is_file_empty = (cleaned_gcov_lines.count <= 0)
@@ -142,7 +164,10 @@ module Slather
142
164
  builder = Nokogiri::HTML::Builder.with(template.at('#reports')) { |cov|
143
165
  cov.h2(:class => "cov_title") {
144
166
  cov.span("Coverage for \"#{filename}\"" + (!is_file_empty ? " : " : ""))
167
+ cov.span("Lines: ") unless is_file_empty
145
168
  cov.span("#{decimal_f(percentage)}%", :class => class_for_coverage_percentage(percentage)) unless is_file_empty
169
+ cov.span(" Branches: ") unless is_file_empty
170
+ cov.span("#{decimal_f(branch_percentage)}%", :class => class_for_coverage_percentage(branch_percentage)) unless is_file_empty
146
171
  }
147
172
 
148
173
  cov.h4("(#{coverage_file.num_lines_tested} of #{coverage_file.num_lines_testable} relevant lines covered)", :class => "cov_subtitle")
@@ -157,8 +182,9 @@ module Slather
157
182
 
158
183
  cov.table(:class => "source_code") {
159
184
  cleaned_gcov_lines.each do |line|
160
-
161
185
  line_number = coverage_file.line_number_in_line(line)
186
+ missed_regions = coverage_file.branch_region_data[line_number]
187
+ hits = coverage_file.coverage_for_line(line)
162
188
  next unless line_number > 0
163
189
 
164
190
  line_source = line.split(line_number_separator, 3)[2]
@@ -171,7 +197,30 @@ module Slather
171
197
  cov.td(line, :class => classes[idx])
172
198
  else
173
199
  cov.td(:class => classes[idx]) {
174
- cov.pre { cov.code(line, :class => "objc") }
200
+ cov.pre {
201
+ # If the line has coverage and missed regions, split up
202
+ # the line to show regions that weren't covered
203
+ if missed_regions != nil && hits != nil && hits > 0
204
+ regions = missed_regions.map do |region|
205
+ region_start, region_length = region
206
+ if region_length != nil
207
+ line[region_start, region_length]
208
+ else
209
+ line[region_start, line.length - region_start]
210
+ end
211
+ end
212
+ current_line = line
213
+ regions.each do |region|
214
+ covered, remainder = current_line.split(region, 2)
215
+ cov.code(covered, :class => "objc")
216
+ cov.code(region, :class => "objc missed")
217
+ current_line = remainder
218
+ end
219
+ cov.code(current_line, :class => "objc")
220
+ else
221
+ cov.code(line, :class => "objc")
222
+ end
223
+ }
175
224
  }
176
225
  end
177
226
  }
@@ -8,7 +8,7 @@ module Slather
8
8
  include CoverageInfo
9
9
  include CoverallsCoverage
10
10
 
11
- attr_accessor :project, :source, :line_numbers_first, :line_data
11
+ attr_accessor :project, :source, :segments, :line_numbers_first, :line_data
12
12
 
13
13
  def initialize(project, source, line_numbers_first)
14
14
  self.project = project
@@ -188,7 +188,47 @@ module Slather
188
188
 
189
189
  def branch_coverage_data
190
190
  @branch_coverage_data ||= begin
191
- Hash.new
191
+ branch_coverage_data = Hash.new
192
+
193
+ self.segments.each do |segment|
194
+ line, col, hits, has_count, *rest = segment
195
+ next if !has_count
196
+ if branch_coverage_data.key?(line)
197
+ branch_coverage_data[line] = branch_coverage_data[line] + [hits]
198
+ else
199
+ branch_coverage_data[line] = [hits]
200
+ end
201
+ end
202
+
203
+ branch_coverage_data
204
+ end
205
+ end
206
+
207
+ def branch_region_data
208
+ @branch_region_data ||= begin
209
+ branch_region_data = Hash.new
210
+ region_start = nil
211
+ current_line = 0
212
+ @segments ||= []
213
+ @segments.each do |segment|
214
+ line, col, hits, has_count, *rest = segment
215
+ # Make column 0 based index
216
+ col = col - 1
217
+ if hits == 0 && has_count
218
+ current_line = line
219
+ region_start = col
220
+ elsif region_start != nil && hits > 0 && has_count
221
+ # if the region wrapped to a new line before ending, put nil to indicate it didnt end on this line
222
+ region_end = line == current_line ? col - region_start : nil
223
+ if branch_region_data.key?(current_line)
224
+ branch_region_data[current_line] << [region_start, region_end]
225
+ else
226
+ branch_region_data[current_line] = [[region_start, region_end]]
227
+ end
228
+ region_start = nil
229
+ end
230
+ end
231
+ branch_region_data
192
232
  end
193
233
  end
194
234
 
@@ -135,7 +135,12 @@ module Slather
135
135
  coverage_json = JSON.parse(coverage_json_string)
136
136
  coverage_json["data"].reduce([]) do |result, chunk|
137
137
  result.concat(chunk["files"].map do |file|
138
- Pathname(file["filename"]).realpath
138
+ filename = file["filename"]
139
+ path = Pathname(filename)
140
+ # Don't crash if the file doesn't exist on disk.
141
+ # This may happen for autogenerated files that have been deleted.
142
+ filename = path.exist? ? path.realpath : filename
143
+ {"filename" => filename, "segments" => file["segments"]}
139
144
  end)
140
145
  end
141
146
  end
@@ -162,13 +167,24 @@ module Slather
162
167
  end
163
168
  private :create_coverage_files_for_binary
164
169
 
165
- def create_coverage_files(binary_path, pathnames)
170
+ def create_coverage_files(binary_path, path_objects)
166
171
  line_numbers_first = Gem::Version.new(self.llvm_version) >= Gem::Version.new('8.1.0')
172
+ # get just file names from the path objects
173
+ pathnames = path_objects.map { |path_obj| path_obj["filename"] }.compact
174
+ # Map of path name => segment array
175
+ paths_to_segments = path_objects.reduce(Hash.new) do |hash, path_obj|
176
+ hash[path_obj["filename"]] = path_obj["segments"]
177
+ hash
178
+ end
167
179
  files = create_profdata(binary_path, pathnames)
168
180
  files.map do |source|
169
181
  coverage_file = coverage_file_class.new(self, source, line_numbers_first)
170
182
  # If a single source file is used, the resulting output does not contain the file name.
171
183
  coverage_file.source_file_pathname = pathnames.first if pathnames.count == 1
184
+ # if there is segment data for the given path, add it to the coverage_file
185
+ if paths_to_segments.key?(coverage_file.source_file_pathname)
186
+ coverage_file.segments = paths_to_segments[coverage_file.source_file_pathname]
187
+ end
172
188
  !coverage_file.ignored? ? coverage_file : nil
173
189
  end.compact
174
190
  end
@@ -277,7 +293,10 @@ module Slather
277
293
  if self.arch
278
294
  llvm_cov_args << "--arch" << self.arch
279
295
  end
280
- `xcrun llvm-cov #{llvm_cov_args.shelljoin} #{source_files.shelljoin}`
296
+
297
+ # POSIX systems have an ARG_MAX for the maximum total length of the command line, so the command may fail with an error message of "Argument list too long".
298
+ # Using the xargs command we can break the list of source_files into sublists small enough to be acceptable.
299
+ `printf '%s\\0' #{source_files.shelljoin} | xargs -0 xcrun llvm-cov #{llvm_cov_args.shelljoin}`
281
300
  end
282
301
  private :unsafe_profdata_llvm_cov_output
283
302
 
@@ -568,6 +587,19 @@ module Slather
568
587
  def find_buildable_names(xcscheme)
569
588
  found_buildable_names = []
570
589
 
590
+ # enumerate code coverage targets
591
+ begin
592
+ code_coverage_targets = xcscheme.test_action.xml_element.elements['CodeCoverageTargets']
593
+ targets = code_coverage_targets.map do |node|
594
+ Xcodeproj::XCScheme::BuildableReference.new(node) if node.is_a?(REXML::Element)
595
+ end.compact
596
+ buildable_names = targets.each do |target|
597
+ found_buildable_names.push(target.buildable_name)
598
+ end
599
+ rescue
600
+ # just in case if there are no entries in the test action
601
+ end
602
+
571
603
  # enumerate build action entries
572
604
  begin
573
605
  xcscheme.build_action.entries.each do |entry|