slather 2.1.0 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3f63502fe53f807b2802d7cd0676e199f040b311
4
- data.tar.gz: 9516ad8c703aa3d40f22e796e1c3a64a8dc780ca
3
+ metadata.gz: eeb8cebaec1537b632d62c19245da2c00af5730e
4
+ data.tar.gz: 0b7ca2c1e79d283e9899b969012f943d611ed0be
5
5
  SHA512:
6
- metadata.gz: 2ced3c9407d4dd0e50150361f9870a3c211eda077667d5a4cca1287e5d685b4327c9be2a9a23b4b774ff99e2ef4e3826ccee1f5034fe93980a88fa69c3d3fd2e
7
- data.tar.gz: ed0d9167ca9b8f9a7001d00a09b4c53a6c7b16c18653abac0a7c41c9ce81b1ddbd3a3cedfc8ce9537497d6d99da38192c825629e7ab034065215be92842b422f
6
+ metadata.gz: 81c1fd17cba7d152635a93aaf546bd1cfa9fc1040dd92da8b690486a913178e254ce28ae753200d2bc171cad0d586f2304a4b21d6683e364cc321bb77c88f0e0
7
+ data.tar.gz: 3f0a80e2845b883f7ed3867a68f5717da9d85ea62bfa07442195017b735e21e667a244cf90924cb99a43193d8256d2293f48bc6f5aa9cef6570d856537c3befd
@@ -3,6 +3,36 @@
3
3
  ## master
4
4
 
5
5
 
6
+ ## v2.2.0
7
+
8
+ * Fix nil crash in `project.rb` derived_data_path
9
+ [bootstraponline](https://github.com/bootstraponline)
10
+ [#203](https://github.com/SlatherOrg/slather/pull/203)
11
+
12
+ * Fix for correct line number for lines that are hit thousands or millions of time in llvm-cov.
13
+ [Mihai Parv](https://github.com/mihaiparv)
14
+ [#202](https://github.com/SlatherOrg/slather/pull/202), [#196](https://github.com/SlatherOrg/slather/issues/196)
15
+
16
+ * Generate coverate for multiple binaries by passing multiple `--binary-basename` or `--binary-file` arguments, or by using an array in `.slather.yml`
17
+ [Kent Sutherland](https://github.com/ksuther)
18
+ [#188](https://github.com/SlatherOrg/slather/pull/188)
19
+
20
+ * Support for specifying source file patterns using the `--source-files` option or the source_files key in `.slather.yml`
21
+ [Matej Bukovinski](https://github.com/matej)
22
+ [#201](https://github.com/SlatherOrg/slather/pull/201)
23
+
24
+ * Improve getting schemes. Looks for user scheme in case no shared scheme is found.
25
+ [Matyas Hlavacek](https://github.com/matyashlavacek)
26
+ [#182](https://github.com/SlatherOrg/slather/issues/182)
27
+
28
+ * Search Xcode workspaces for schemes. Workspaces are checked if no matching scheme is found in the project.
29
+ [Kent Sutherland](https://github.com/ksuther)
30
+ [#193](https://github.com/SlatherOrg/slather/pull/193), [#191](https://github.com/SlatherOrg/slather/issues/191)
31
+
32
+ * Fix for hit counts in thousands or millions being output as floats intead of integers
33
+ [Carl Hill-Popper](https://github.com/chillpop)
34
+ [#190](https://github.com/SlatherOrg/slather/pull/190)
35
+
6
36
  ## v2.1.0
7
37
 
8
38
  * Support for Xcode workspaces. Define `workspace` configuration in `.slather.yml` or use the `--workspace` argument if you build in a workspace.
data/README.md CHANGED
@@ -55,7 +55,7 @@ If you use a workspace in Xcode you need to specify it:
55
55
  $ slather coverage -s --scheme YourXcodeSchemeName --workspace path/to/workspace.xcworkspace path/to/project.xcodeproj
56
56
  ```
57
57
 
58
- ### Setup for Xode 5 and 6
58
+ ### Setup for Xcode 5 and 6
59
59
 
60
60
  Run this command to enable the `Generate Test Coverage` and `Instrument Program Flow` flags for your project:
61
61
 
@@ -24,8 +24,9 @@ class CoverageCommand < Clamp::Command
24
24
  option ["--input-format"], "INPUT_FORMAT", "Input format (gcov, profdata)"
25
25
  option ["--scheme"], "SCHEME", "The scheme for which the coverage was generated"
26
26
  option ["--workspace"], "WORKSPACE", "The workspace that the project was built in"
27
- option ["--binary-file"], "BINARY_FILE", "The binary file against the which the coverage will be run"
28
- option ["--binary-basename"], "BINARY_BASENAME", "Basename of the file against which the coverage will be run"
27
+ option ["--binary-file"], "BINARY_FILE", "The binary file against the which the coverage will be run", :multivalued => true
28
+ option ["--binary-basename"], "BINARY_BASENAME", "Basename of the file against which the coverage will be run", :multivalued => true
29
+ option ["--source-files"], "SOURCE_FILES", "A Dir.glob compatible pattern used to limit the lookup to specific source files. Ignored in gcov mode.", :multivalued => true
29
30
 
30
31
  def execute
31
32
  puts "Slathering..."
@@ -42,6 +43,7 @@ class CoverageCommand < Clamp::Command
42
43
  setup_workspace
43
44
  setup_binary_file
44
45
  setup_binary_basename
46
+ setup_source_files
45
47
 
46
48
  project.configure
47
49
 
@@ -127,10 +129,14 @@ class CoverageCommand < Clamp::Command
127
129
  end
128
130
 
129
131
  def setup_binary_file
130
- project.binary_file = binary_file
132
+ project.binary_file = binary_file_list if !binary_file_list.empty?
131
133
  end
132
134
 
133
135
  def setup_binary_basename
134
- project.binary_basename = binary_basename
136
+ project.binary_basename = binary_basename_list if !binary_basename_list.empty?
137
+ end
138
+
139
+ def setup_source_files
140
+ project.source_files = source_files_list if !source_files_list.empty?
135
141
  end
136
142
  end
@@ -16,13 +16,18 @@ module Slather
16
16
  end
17
17
 
18
18
  def create_line_data
19
- all_lines = self.source.split("\n")[1..-1]
19
+ all_lines = source_code_lines
20
20
  line_data = Hash.new
21
21
  all_lines.each { |line| line_data[line_number_in_line(line)] = line }
22
22
  self.line_data = line_data
23
23
  end
24
24
  private :create_line_data
25
25
 
26
+ def path_on_first_line?
27
+ path = self.source.split("\n")[0].sub ":", ""
28
+ !path.include?("1|//")
29
+ end
30
+
26
31
  def source_file_pathname
27
32
  @source_file_pathname ||= begin
28
33
  path = self.source.split("\n")[0].sub ":", ""
@@ -30,17 +35,25 @@ module Slather
30
35
  end
31
36
  end
32
37
 
38
+ def source_file_pathname= (source_file_pathname)
39
+ @source_file_pathname = source_file_pathname
40
+ end
41
+
33
42
  def source_file
34
43
  File.new(source_file_pathname)
35
44
  end
36
45
 
46
+ def source_code_lines
47
+ self.source.split("\n")[(path_on_first_line? ? 1 : 0)..-1]
48
+ end
49
+
37
50
  def source_data
38
51
  all_lines.join("\n")
39
52
  end
40
53
 
41
54
  def all_lines
42
55
  if @all_lines == nil
43
- @all_lines = self.source.split("\n")[1..-1]
56
+ @all_lines = source_code_lines
44
57
  end
45
58
  @all_lines
46
59
  end
@@ -61,12 +74,23 @@ module Slather
61
74
  when /[0-9]+/
62
75
  return match.to_i
63
76
  end
77
+ else
78
+ # llvm-cov outputs hit counts as 25.3k or 3.8M, so check this pattern as well
79
+ did_match = line =~ /^(\s*)(\d+\.\d+)(k|M)\|(\s*)(\d+)\|/
80
+
81
+ if did_match
82
+ match = $5.strip
83
+ case match
84
+ when /[0-9]+/
85
+ return match.to_i
86
+ end
87
+ end
64
88
  end
65
89
  0
66
90
  end
67
91
 
68
92
  def line_coverage_data
69
- self.source.split("\n")[1..-1].map do |line|
93
+ source_code_lines.map do |line|
70
94
  coverage_for_line(line)
71
95
  end
72
96
  end
@@ -83,7 +107,7 @@ module Slather
83
107
  count = $2.strip
84
108
  units = $3 == 'k' ? 1000 : 1000000
85
109
 
86
- count.to_f * units
110
+ (count.to_f * units).to_i
87
111
  else
88
112
  return nil
89
113
  end
@@ -133,7 +157,10 @@ module Slather
133
157
  def platform_ignore_list
134
158
  ["MacOSX.platform/Developer/Library/Frameworks/XCTest.framework/Headers/XCTestAssertionsImpl.h",
135
159
  "MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/objc/objc.h",
136
- "MacOSX.platform/Developer/Library/Frameworks/XCTest.framework/Headers/XCTestAssertions.h"]
160
+ "MacOSX.platform/Developer/Library/Frameworks/XCTest.framework/Headers/XCTestAssertions.h",
161
+ # This indicates a llvm-cov coverage warning (occurs if a passed in source file
162
+ # is not covered or with ccache in some cases).
163
+ "isn't covered."]
137
164
  end
138
165
  private :platform_ignore_list
139
166
  end
@@ -51,7 +51,7 @@ module Slather
51
51
  class Project < Xcodeproj::Project
52
52
 
53
53
  attr_accessor :build_directory, :ignore_list, :ci_service, :coverage_service, :coverage_access_token, :source_directory,
54
- :output_directory, :xcodeproj, :show_html, :verbose_mode, :input_format, :scheme, :workspace, :binary_file, :binary_basename
54
+ :output_directory, :xcodeproj, :show_html, :verbose_mode, :input_format, :scheme, :workspace, :binary_file, :binary_basename, :source_files
55
55
 
56
56
  alias_method :setup_for_coverage, :slather_setup_for_coverage
57
57
 
@@ -82,10 +82,13 @@ module Slather
82
82
  buildAction = nil
83
83
  end
84
84
 
85
- build_settings = `xcodebuild #{projectOrWorkspaceArgument} #{schemeArgument} -showBuildSettings #{buildAction}`
85
+ # redirect stderr to avoid xcodebuild errors being printed.
86
+ build_settings = `xcodebuild #{projectOrWorkspaceArgument} #{schemeArgument} -showBuildSettings #{buildAction} 2>&1`
86
87
 
87
88
  if build_settings
88
- derived_data_path = build_settings.match(/ OBJROOT = (.+)/)[1]
89
+ derived_data_path = build_settings.match(/ OBJROOT = (.+)/)
90
+ # when match fails derived_data_path is nil
91
+ derived_data_path = derived_data_path[1] if derived_data_path
89
92
  end
90
93
 
91
94
  if derived_data_path == nil
@@ -121,12 +124,28 @@ module Slather
121
124
  private :gcov_coverage_files
122
125
 
123
126
  def profdata_coverage_files
124
- files = profdata_llvm_cov_output.split("\n\n")
127
+ coverage_files = []
128
+ source_files = find_source_files || []
129
+
130
+ if self.binary_file
131
+ self.binary_file.each do |binary_path|
132
+ files = profdata_llvm_cov_output(binary_path, source_files).split("\n\n")
133
+
134
+ coverage_files.concat(files.map do |source|
135
+ coverage_file = coverage_file_class.new(self, source)
136
+ # If a single source file is used, the resulting output does not contain the file name.
137
+ coverage_file.source_file_pathname = source_files.first if source_files.count == 1
138
+ !coverage_file.ignored? ? coverage_file : nil
139
+ end.compact)
140
+
141
+ if !source_files.empty?
142
+ coverage_file_paths = coverage_files.map { |file| file.source_file_pathname }.to_set
143
+ source_files.select! { |path| !coverage_file_paths.include?(path) }
144
+ end
145
+ end
146
+ end
125
147
 
126
- files.map do |source|
127
- coverage_file = coverage_file_class.new(self, source)
128
- !coverage_file.ignored? ? coverage_file : nil
129
- end.compact
148
+ coverage_files
130
149
  end
131
150
  private :profdata_coverage_files
132
151
 
@@ -173,23 +192,23 @@ module Slather
173
192
  end
174
193
  private :profdata_file
175
194
 
176
- def unsafe_profdata_llvm_cov_output
195
+ def unsafe_profdata_llvm_cov_output(binary_path, source_files)
177
196
  profdata_file_arg = profdata_file
178
197
  if profdata_file_arg == nil
179
198
  raise StandardError, "No Coverage.profdata files found. Please make sure the \"Code Coverage\" checkbox is enabled in your scheme's Test action or the build_directory property is set."
180
199
  end
181
200
 
182
- if self.binary_file == nil
201
+ if binary_path == nil
183
202
  raise StandardError, "No binary file found."
184
203
  end
185
204
 
186
- llvm_cov_args = %W(show -instr-profile #{profdata_file_arg} #{self.binary_file})
187
- `xcrun llvm-cov #{llvm_cov_args.shelljoin}`
205
+ llvm_cov_args = %W(show -instr-profile #{profdata_file_arg} #{binary_path})
206
+ `xcrun llvm-cov #{llvm_cov_args.shelljoin} #{source_files.shelljoin}`
188
207
  end
189
208
  private :unsafe_profdata_llvm_cov_output
190
209
 
191
- def profdata_llvm_cov_output
192
- unsafe_profdata_llvm_cov_output.encode!('UTF-8', 'binary', :invalid => :replace, undef: :replace)
210
+ def profdata_llvm_cov_output(binary_path, source_files)
211
+ unsafe_profdata_llvm_cov_output(binary_path, source_files).encode!('UTF-8', 'binary', :invalid => :replace, undef: :replace)
193
212
  end
194
213
  private :profdata_llvm_cov_output
195
214
 
@@ -228,7 +247,11 @@ module Slather
228
247
 
229
248
  if self.verbose_mode
230
249
  puts "\nProcessing coverage file: #{profdata_file}"
231
- puts "Against binary file: #{self.binary_file}\n\n"
250
+ puts "Against binary files:"
251
+ self.binary_file.each do |binary_file|
252
+ puts "\t#{binary_file}"
253
+ end
254
+ puts "\n"
232
255
  end
233
256
  end
234
257
 
@@ -311,24 +334,46 @@ module Slather
311
334
 
312
335
  def configure_binary_file
313
336
  if self.input_format == "profdata"
314
- self.binary_file ||= self.class.yml["binary_file"] || File.expand_path(find_binary_file)
337
+ self.binary_file = load_option_array("binary_file") || find_binary_files
315
338
  end
316
339
  end
317
340
 
318
341
  def find_binary_file_in_bundle(bundle_file)
319
- bundle_file_noext = File.basename(bundle_file, File.extname(bundle_file))
320
- Dir["#{bundle_file}/**/#{bundle_file_noext}"].first
342
+ if File.directory? bundle_file
343
+ bundle_file_noext = File.basename(bundle_file, File.extname(bundle_file))
344
+ Dir["#{bundle_file}/**/#{bundle_file_noext}"].first
345
+ else
346
+ bundle_file
347
+ end
321
348
  end
322
349
 
323
- def find_binary_file
324
- binary_basename = self.binary_basename || self.class.yml["binary_basename"] || nil
350
+ def find_binary_files
351
+ binary_basename = load_option_array("binary_basename")
352
+ found_binaries = []
325
353
 
326
354
  # Get scheme info out of the xcodeproj
327
355
  if self.scheme
328
356
  schemes_path = Xcodeproj::XCScheme.shared_data_dir(self.path)
329
357
  xcscheme_path = "#{schemes_path + self.scheme}.xcscheme"
330
358
 
331
- raise StandardError, "No shared scheme named '#{self.scheme}' found in #{self.path}" unless File.exists? xcscheme_path
359
+ # Try to look inside 'xcuserdata' if the scheme is not found in 'xcshareddata'
360
+ if !File.file?(xcscheme_path)
361
+ schemes_path = Xcodeproj::XCScheme.user_data_dir(self.path)
362
+ xcscheme_path = "#{schemes_path + self.scheme}.xcscheme"
363
+ end
364
+
365
+ if self.workspace and !File.file?(xcscheme_path)
366
+ # No scheme was found in the xcodeproj, check the workspace
367
+ schemes_path = Xcodeproj::XCScheme.shared_data_dir(self.workspace)
368
+ xcscheme_path = "#{schemes_path + self.scheme}.xcscheme"
369
+
370
+ if !File.file?(xcscheme_path)
371
+ schemes_path = Xcodeproj::XCScheme.user_data_dir(self.workspace)
372
+ xcscheme_path = "#{schemes_path + self.scheme}.xcscheme"
373
+ end
374
+ end
375
+
376
+ raise StandardError, "No scheme named '#{self.scheme}' found in #{self.path}" unless File.exists? xcscheme_path
332
377
 
333
378
  xcscheme = Xcodeproj::XCScheme.new(xcscheme_path)
334
379
 
@@ -345,19 +390,26 @@ module Slather
345
390
 
346
391
  configuration = xcscheme.test_action.build_configuration
347
392
 
348
- search_for = binary_basename || buildable_name
349
- found_product = Dir["#{profdata_coverage_dir}/Products/#{configuration}*/#{search_for}*"].sort { |x, y|
350
- # Sort the matches without the file extension to ensure better matches when there are multiple candidates
351
- # For example, if the binary_basename is Test then we want Test.app to be matched before Test Helper.app
352
- File.basename(x, File.extname(x)) <=> File.basename(y, File.extname(y))
353
- }.reject { |path|
354
- path.end_with? ".dSYM"
355
- }.first
393
+ search_list = binary_basename || [buildable_name]
394
+
395
+ search_list.each do |search_for|
396
+ found_product = Dir["#{profdata_coverage_dir}/Products/#{configuration}*/#{search_for}*"].sort { |x, y|
397
+ # Sort the matches without the file extension to ensure better matches when there are multiple candidates
398
+ # For example, if the binary_basename is Test then we want Test.app to be matched before Test Helper.app
399
+ File.basename(x, File.extname(x)) <=> File.basename(y, File.extname(y))
400
+ }.reject { |path|
401
+ path.end_with? ".dSYM"
402
+ }.first
403
+
404
+ if found_product and File.directory? found_product
405
+ found_binary = find_binary_file_in_bundle(found_product)
406
+ else
407
+ found_binary = found_product
408
+ end
356
409
 
357
- if found_product and File.directory? found_product
358
- found_binary = find_binary_file_in_bundle(found_product)
359
- else
360
- found_binary = found_product
410
+ if found_binary
411
+ found_binaries.push(found_binary)
412
+ end
361
413
  end
362
414
  else
363
415
  xctest_bundle = Dir["#{profdata_coverage_dir}/**/*.xctest"].reject { |bundle|
@@ -366,26 +418,62 @@ module Slather
366
418
  }.first
367
419
 
368
420
  # Find the matching binary file
369
- search_for = binary_basename || '*'
370
- xctest_bundle_file_directory = Pathname.new(xctest_bundle).dirname
371
- app_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.app"].first
372
- dynamic_lib_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.framework"].first
373
- matched_xctest_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.xctest"].first
421
+ search_list = binary_basename || ['*']
422
+
423
+ search_list.each do |search_for|
424
+ xctest_bundle_file_directory = Pathname.new(xctest_bundle).dirname
425
+ app_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.app"].first
426
+ matched_xctest_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.xctest"].first
427
+ dynamic_lib_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.{framework,dylib}"].first
374
428
 
375
- if app_bundle != nil
429
+ if app_bundle != nil
376
430
  found_binary = find_binary_file_in_bundle(app_bundle)
377
- elsif dynamic_lib_bundle != nil
378
- found_binary = find_binary_file_in_bundle(dynamic_lib_bundle)
379
- elsif matched_xctest_bundle != nil
431
+ elsif matched_xctest_bundle != nil
380
432
  found_binary = find_binary_file_in_bundle(matched_xctest_bundle)
381
- else
433
+ elsif dynamic_lib_bundle != nil
434
+ found_binary = find_binary_file_in_bundle(dynamic_lib_bundle)
435
+ else
382
436
  found_binary = find_binary_file_in_bundle(xctest_bundle)
437
+ end
438
+
439
+ if found_binary
440
+ found_binaries.push(found_binary)
441
+ end
383
442
  end
384
443
  end
385
444
 
386
- raise StandardError, "No product binary found in #{profdata_coverage_dir}." unless found_binary != nil
445
+ raise StandardError, "No product binary found in #{profdata_coverage_dir}." unless found_binaries.count > 0
387
446
 
388
- found_binary
447
+ found_binaries.map { |binary| File.expand_path(binary) }
448
+ end
449
+
450
+ def find_source_files
451
+ source_files = load_option_array("source_files")
452
+ return if source_files.nil?
453
+
454
+ current_dir = Pathname("./").realpath
455
+ paths = source_files.flat_map { |pattern| Dir.glob(pattern) }.uniq
456
+
457
+ paths.map do |path|
458
+ source_file_absolute_path = Pathname(path).realpath
459
+ source_file_relative_path = source_file_absolute_path.relative_path_from(current_dir)
460
+ self.ignore_list.any? { |ignore| File.fnmatch(ignore, source_file_relative_path) } ? nil : source_file_absolute_path
461
+ end.compact
462
+ end
463
+
464
+ def load_option_array(option)
465
+ value = self.send(option.to_sym)
466
+ # Only load if a value is not already set
467
+ if !value
468
+ value_yml = self.class.yml[option]
469
+ # Need to check the type in the config file because it can be a string or array
470
+ if value_yml and value_yml.is_a? Array
471
+ value = value_yml
472
+ elsif value_yml
473
+ value = [value_yml]
474
+ end
475
+ end
476
+ value
389
477
  end
390
478
  end
391
479
  end