slather 2.1.0 → 2.2.0

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
  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