slather 0.0.23 → 0.0.31

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,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MDFkYzUwNDJlZmUyYTc5YmI0ZjMzMmNhNTBlZTY1NTc0ZWJmNTA5Mg==
4
+ NjczM2RkMDA4MTllMjlkNjJmZTkwOTQyOGZiMTkzMjY4ZTUwZDhiNg==
5
5
  data.tar.gz: !binary |-
6
- ZGE4NTI0NWVkYzdjYmQyYTU2YTY2NmNhMDdmOTFlMGQ0MjI0MjhiZA==
6
+ MWIwNjIxZGNiNDYxNjM0ZjU0NmNjYmY4MGRiMjZiNjQ0YjkxZGI4Yg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZDBlY2Q0NmUwZDBiN2VjMjQzYWU4OTZkY2U2ZGRkOGEwMWU2MDRiM2U5MmM3
10
- MjgwMTBkZDEyOGFlMmRkMDg1NWIzYzA3MTJhZTJiODAwZTdjZjAwZDVkNjZl
11
- M2U4ZDY5YWY1NmU3OTdiZjRkMGJlODIyOTg0MmIyZmM5NjFlYzg=
9
+ OWQ0NzczYjMzMTI3NTNlMjcwMWI5NTJiNTg2MDAxMWNjMWM3ODM5ZjUzZjAz
10
+ YjRmMDBhNmVjMzNhOTc4MzQwY2Q4ZTVmZTdmOGNhMTA4ZWVlNTQ1ODZiODEw
11
+ YjEzY2M0Y2FhOGY0NjRiNGMyZTFkNDBkYmIyMjFmNmQxOGY1MTg=
12
12
  data.tar.gz: !binary |-
13
- OWYzYWRlYjI4MmI5YjRjZmI0NzE5NzQyNmFiOWVmNWE3YmQ4OWNhMDBhYjU1
14
- ZjE3MjJhZTZmNjA4YThhYmNlNmVjMTEyNmU4ZjY1NzMyMWE3NTU5OTg3MGM2
15
- ZmE1ODY4Y2Y0MmY5YTIxZjdmM2IyMmUxMThkZThmZDA1ZDg2OTA=
13
+ MzhiZWI2ZDVlYmUwM2JiMGRiNjhlMmEzYTljNjZiNTMyZDIxOGMxMzVkODgz
14
+ MzVhMzVlNjkxMzcwYzVlNThmNjMzZjRhYzIwNjdkOTNjYWM5M2E2ZWJhYjMx
15
+ ZmQyYTJmOWNkZjE3ZmFlZGUwODE0NGNiMTEyODhhZWI4OWNiMWE=
data/.gitignore CHANGED
@@ -21,4 +21,22 @@ tmp
21
21
  *.a
22
22
  mkmf.log
23
23
 
24
+ # Xcode
25
+ #
26
+ build/
27
+ *.pbxuser
28
+ !default.pbxuser
29
+ *.mode1v3
30
+ !default.mode1v3
31
+ *.mode2v3
32
+ !default.mode2v3
33
+ *.perspectivev3
34
+ !default.perspectivev3
35
+ xcuserdata
36
+ *.xccheckout
37
+ *.moved-aside
38
+ DerivedData
39
+ *.hmap
40
+ *.ipa
41
+ *.xcuserstate
24
42
  *.DS_Store
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # Slather
2
2
 
3
- TODO: Write a gem description
3
+ Generate test coverage reports for Xcode projects. Apply tests liberally.
4
+
5
+ | [Parsimmon](https://github.com/ayanonagon/Parsimmon) | [![Parsimmon Coverage](https://coveralls.io/repos/ayanonagon/Parsimmon/badge.png?branch=master)](https://coveralls.io/r/ayanonagon/Parsimmon?branch=master) |
6
+ |------------------------------------------------------------|:----------------------------------------------------:|
4
7
 
5
8
  ## Installation
6
9
 
@@ -12,18 +15,54 @@ And then execute:
12
15
 
13
16
  $ bundle
14
17
 
15
- Or install it yourself as:
18
+ ## Usage
16
19
 
17
- $ gem install slather
20
+ Setup your project for test coverage:
18
21
 
19
- ## Usage
22
+ $ slather setup path/to/project.xcodeproj
23
+
24
+ This will enable the `Generate Test Coverage` and `Instrument Program Flow` flags for your project.
25
+
26
+
27
+ To test if you're ready to generate test coverage, run your test suite on your project, and then run:
28
+
29
+ $ slather coverage -s path/to/project.xcodeproj
30
+
31
+ ### Coveralls
32
+
33
+ Login to https://coveralls.io/ and enable your repository. Right now, `slather` only supports coveralls via Travis CI.
34
+
35
+ Make a `.slather.yml` file:
20
36
 
21
- TODO: Write usage instructions here
37
+ ```yml
38
+ # .slather.yml
39
+
40
+ coverage_service: coveralls
41
+ xcodeproj: path/to/project.xcodeproj
42
+ ignore:
43
+ - ExamplePodCode/*
44
+ - ProjectTestsGroup/*
45
+ ```
46
+
47
+ And then in your `.travis.yml`, call `slather` after a successful_build
48
+
49
+ ```yml
50
+ # .travis.yml
51
+
52
+ after_success: slather
53
+ ```
54
+
55
+ ### Custom Build Directory
56
+
57
+ Slather will look for the test coverage files in `DerivedData` by default. If you send build output to a custom location, like [this](https://github.com/erikdoe/ocmock/blob/master/Tools/travis.sh#L12), then you should also set the `build_directory` property in `.slather.yml`
22
58
 
23
59
  ## Contributing
24
60
 
25
- 1. Fork it ( https://github.com/[my-github-username]/slather/fork )
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create a new Pull Request
61
+ We'd love to see your ideas for improving this library! The best way to contribute is by submitting a pull request. We'll do our best to respond to your patch as soon as possible. You can also submit a [new Github issue](https://github.com/venmo/slather/issues/new) if you find bugs or have questions. :octocat:
62
+
63
+ Please make sure to follow our general coding style and add test coverage for new features!
64
+
65
+ ## Contributors
66
+
67
+ * [@tpoulos](https://github.com/tpoulos), the perfect logo.
68
+ * [@ayanonagon](https://github.com/ayanonagon) and [@kylef](https://github.com/benzguo), feedback and testing.
data/bin/slather CHANGED
@@ -1,19 +1,85 @@
1
1
  #!/usr/bin/env ruby
2
-
3
2
  require 'clamp'
4
- require File.join(File.dirname(__FILE__), '..', 'lib', 'slather')
3
+ require 'yaml'
4
+ require File.join(File.dirname(__FILE__), '../lib/slather')
5
5
 
6
6
  Clamp do
7
7
 
8
- parameter "xcodeproj", "Path to the xcodeproj", :attribute_name => :xcodeproj_path
9
- option ["--build-directory", "-b"], "BUILD_DIRECTORY", "The directory where gcno files will be written to. Defaults to derived data."
8
+ self.default_subcommand = "coverage"
9
+
10
+ subcommand "coverage", "Computes coverage for the supplised project" do
11
+
12
+ parameter "[xcodeproj]", "Path to the xcodeproj", :attribute_name => :xcodeproj_path
13
+
14
+ option ["--travis", "-t"], :flag, "Indicate that the builds are running on Travis CI"
15
+
16
+ option ["--coveralls", "-c"], :flag, "Post coverage results to coveralls"
17
+ option ["--simple-output", "-s"], :flag, "Post coverage results to coveralls"
18
+
19
+ option ["--build-directory", "-b"], "BUILD_DIRECTORY", "The directory where gcno files will be written to. Defaults to derived data."
20
+ option ["--ignore", "-i"], "IGNORE", "ignore files conforming to a path", :multivalued => true
21
+
22
+ def execute
23
+ puts "Slathering..."
24
+
25
+ setup_service_name
26
+ setup_ignore_list
27
+ setup_build_directory
28
+ setup_coverage_service
29
+
30
+ post
31
+
32
+ puts "Slathered"
33
+ end
34
+
35
+ def setup_build_directory
36
+ project.build_directory = build_directory if build_directory
37
+ end
38
+
39
+ def setup_ignore_list
40
+ project.ignore_list = ignore_list if !ignore_list.empty?
41
+ end
42
+
43
+ def setup_service_name
44
+ if travis?
45
+ project.ci_service = :travis_ci
46
+ end
47
+ end
48
+
49
+ def post
50
+ project.post
51
+ end
52
+
53
+ def project
54
+ @project ||= begin
55
+ xcodeproj_path_to_open = xcodeproj_path || Slather::Project.yml["xcodeproj"]
56
+ if xcodeproj_path_to_open
57
+ project = Slather::Project.open(xcodeproj_path_to_open)
58
+ else
59
+ raise StandardError, "Must provide an xcodeproj through .slather.yml"
60
+ end
61
+ end
62
+ end
63
+
64
+ def setup_coverage_service
65
+ if coveralls?
66
+ project.coverage_service = :coveralls
67
+ elsif simple_output?
68
+ project.coverage_service = :terminal
69
+ end
70
+ end
10
71
 
11
- def execute
12
- puts "slathering..."
13
- project = Slather::Project.open(xcodeproj_path)
14
- project.build_directory = build_directory
15
- project.post_to_coveralls
16
- puts "done!"
17
72
  end
18
73
 
74
+ subcommand "setup", "Configures an xcodeproj for test coverage generation" do
75
+
76
+ parameter "[xcodeproj]", "Path to the xcodeproj", :attribute_name => :xcodeproj_path
77
+
78
+ def execute
79
+ project = Slather::Project.open(xcodeproj_path)
80
+ project.setup_for_coverage
81
+ project.save
82
+ end
83
+
84
+ end
19
85
  end
data/lib/slather.rb CHANGED
@@ -2,6 +2,11 @@ require 'slather/version'
2
2
  require 'slather/project'
3
3
  require 'slather/coverage_file'
4
4
  require 'slather/coveralls_coverage_file'
5
+ require 'slather/coverage_service/coveralls'
6
+ require 'slather/coverage_service/simple_output'
5
7
 
6
8
  module Slather
9
+
10
+ Encoding.default_external = "utf-8"
11
+
7
12
  end
@@ -3,16 +3,17 @@ module Slather
3
3
 
4
4
  attr_accessor :project, :gcno_file_pathname
5
5
 
6
- def initialize(gcno_file_pathname)
7
- @gcno_file_pathname = Pathname(gcno_file_pathname)
6
+ def initialize(project, gcno_file_pathname)
7
+ self.project = project
8
+ self.gcno_file_pathname = Pathname(gcno_file_pathname)
8
9
  end
9
10
 
10
11
  def source_file_pathname
11
12
  @source_file_pathname ||= begin
12
13
  base_filename = gcno_file_pathname.basename.sub_ext("")
13
14
  # TODO: Handle Swift
14
- path = Dir["#{project.main_group.real_path}/*/#{base_filename}.m"].first
15
- path && Pathname(path)
15
+ pbx_file = project.files.detect { |pbx_file| pbx_file.real_path.basename.to_s == "#{base_filename}.m" }
16
+ pbx_file && pbx_file.real_path
16
17
  end
17
18
  end
18
19
 
@@ -24,8 +25,8 @@ module Slather
24
25
  source_file.read
25
26
  end
26
27
 
27
- def source_file_pathname_relative_to_project_root
28
- source_file_pathname.relative_path_from(project.main_group.real_path)
28
+ def source_file_pathname_relative_to_repo_root
29
+ source_file_pathname.relative_path_from(Pathname("./").realpath)
29
30
  end
30
31
 
31
32
  def gcov_data
@@ -57,5 +58,11 @@ module Slather
57
58
  end
58
59
  end
59
60
 
61
+ def ignored?
62
+ project.ignore_list.any? do |ignore|
63
+ File.fnmatch(ignore, source_file_pathname_relative_to_repo_root)
64
+ end
65
+ end
66
+
60
67
  end
61
68
  end
@@ -0,0 +1,52 @@
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 coveralls_coverage_data
16
+ if ci_service == :travis_ci
17
+ if travis_job_id
18
+ {
19
+ :service_job_id => travis_job_id,
20
+ :service_name => "travis-ci",
21
+ :source_files => coverage_files.map(&:as_json)
22
+ }.to_json
23
+ else
24
+ raise StandardError, "Environment variable `TRAVIS_JOB_ID` not set. Is this running on a travis build?"
25
+ end
26
+ else
27
+ raise StandardError, "No support for ci named #{ci_service}"
28
+ end
29
+ end
30
+ private :coveralls_coverage_data
31
+
32
+ def post
33
+ f = File.open('coveralls_json_file', 'w+')
34
+ begin
35
+ f.write(coveralls_coverage_data)
36
+ f.close
37
+ `curl -s --form json_file=@#{f.path} #{coveralls_api_jobs_path}`
38
+ rescue StandardError => e
39
+ FileUtils.rm(f)
40
+ raise e
41
+ end
42
+ FileUtils.rm(f)
43
+ end
44
+
45
+ def coveralls_api_jobs_path
46
+ "https://coveralls.io/api/v1/jobs"
47
+ end
48
+ private :coveralls_api_jobs_path
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,32 @@
1
+ module Slather
2
+ module CoverageService
3
+ module SimpleOutput
4
+
5
+ def coverage_file_class
6
+ Slather::CoverallsCoverageFile
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
+ coverage_data = coverage_file.coverage_data.compact
16
+
17
+ lines_tested = coverage_data.select { |cd| cd > 0 }.count
18
+ total_lines = coverage_data.count
19
+ percentage = '%.2f' % [(lines_tested / total_lines.to_f) * 100.0]
20
+
21
+ total_project_lines_tested += lines_tested
22
+ total_project_lines += total_lines
23
+
24
+ puts "#{coverage_file.source_file_pathname_relative_to_repo_root}: #{lines_tested} of #{total_lines} lines (#{percentage}%)"
25
+ end
26
+ total_percentage = '%.2f' % [(total_project_lines_tested / total_project_lines.to_f) * 100.0]
27
+ puts "Test Coverage: #{total_percentage}%"
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -11,7 +11,7 @@ module Slather
11
11
 
12
12
  def as_json
13
13
  {
14
- :name => source_file_pathname_relative_to_project_root.to_s,
14
+ :name => source_file_pathname_relative_to_repo_root.to_s,
15
15
  :source => source_data,
16
16
  :coverage => coverage_data
17
17
  }
@@ -1,11 +1,18 @@
1
1
  require 'fileutils'
2
2
  require 'xcodeproj'
3
3
  require 'json'
4
+ require 'yaml'
4
5
 
5
6
  module Slather
6
7
  class Project < Xcodeproj::Project
7
8
 
8
- attr_accessor :build_directory
9
+ attr_accessor :build_directory, :ignore_list, :ci_service, :coverage_service
10
+
11
+ def self.open(xcodeproj)
12
+ proj = super
13
+ proj.configure_from_yml
14
+ proj
15
+ end
9
16
 
10
17
  def derived_data_dir
11
18
  File.expand_path('~') + "/Library/Developer/Xcode/DerivedData/"
@@ -15,46 +22,74 @@ module Slather
15
22
  def build_directory
16
23
  @build_directory || derived_data_dir
17
24
  end
18
- private :build_directory
19
25
 
20
26
  def coverage_files
21
- Dir["#{build_directory}/**/*.gcno"].map do |file|
22
- coverage_file = Slather::CoverallsCoverageFile.new(file)
23
- coverage_file.project = self
24
- # If there's no source file for this gcno, or the gcno is old, it probably belongs to another project.
25
- if coverage_file.source_file_pathname
26
- stale_seconds_limit = 60
27
- if (Time.now - File.mtime(file) < stale_seconds_limit)
28
- next coverage_file
29
- else
30
- puts "Skipping #{file} -- older than #{stale_seconds_limit} seconds ago."
31
- end
32
- end
33
- next nil
27
+ coverage_files = Dir["#{build_directory}/**/*.gcno"].map do |file|
28
+ coverage_file = coverage_file_class.new(self, file)
29
+ # If there's no source file for this gcno, it probably belongs to another project.
30
+ coverage_file.source_file_pathname && !coverage_file.ignored? ? coverage_file : nil
34
31
  end.compact
32
+
33
+ if coverage_files.empty?
34
+ raise StandardError, "No coverage files found. Are you sure your project is setup for generating coverage files? Try `slather setup your/project.pbxproj`"
35
+ else
36
+ coverage_files
37
+ end
35
38
  end
36
39
  private :coverage_files
37
40
 
38
- def coveralls_coverage_data
39
- {
40
- :service_job_id => ENV['TRAVIS_JOB_ID'],
41
- :service_name => "travis-ci",
42
- :source_files => coverage_files.map(&:as_json)
43
- }.to_json
44
- end
45
- private :coveralls_coverage_data
46
-
47
- def post_to_coveralls
48
- puts "build_directory: #{build_directory}"
49
- puts "gcno files:"
50
- puts Dir["#{build_directory}/**/*.gcno"]
51
- f = File.open('coveralls_json_file', 'w+')
52
- f.write(coveralls_coverage_data)
53
- puts "file data!!!!!"
54
- f.rewind
55
- puts f.read
56
- `curl -s --form json_file=@#{f.path} https://coveralls.io/api/v1/jobs`
57
- FileUtils.rm(f)
41
+ def self.yml_filename
42
+ '.slather.yml'
43
+ end
44
+
45
+ def self.yml
46
+ @yml ||= File.exist?(yml_filename) ? YAML.load_file(yml_filename) : {}
47
+ end
48
+
49
+ def configure_from_yml
50
+ configure_build_directory_from_yml
51
+ configure_ignore_list_from_yml
52
+ configure_ci_service_from_yml
53
+ configure_coverage_service_from_yml
54
+ end
55
+
56
+ def configure_build_directory_from_yml
57
+ self.build_directory = self.class.yml["build_directory"] if self.class.yml["build_directory"] && !@build_directory
58
+ end
59
+
60
+ def configure_ignore_list_from_yml
61
+ self.ignore_list = [(self.class.yml["ignore"] || [])].flatten unless self.ignore_list
62
+ end
63
+
64
+ def configure_ci_service_from_yml
65
+ self.ci_service = (self.class.yml["ci_service"] || :travis_ci) unless self.ci_service
66
+ end
67
+
68
+ def ci_service=(service)
69
+ @ci_service = service && service.to_sym
70
+ end
71
+
72
+ def configure_coverage_service_from_yml
73
+ self.coverage_service = (self.class.yml["coverage_service"] || :terminal) unless coverage_service
74
+ end
75
+
76
+ def coverage_service=(service)
77
+ service = service && service.to_sym
78
+ if service == :coveralls
79
+ extend(Slather::CoverageService::Coveralls)
80
+ elsif service == :terminal
81
+ extend(Slather::CoverageService::SimpleOutput)
82
+ else
83
+ raise ArgumentError, "`#{coverage_service}` is not a valid coverage service. Try `terminal` or `coveralls`"
84
+ end
85
+ @coverage_service = service
86
+ end
87
+
88
+ def setup_for_coverage
89
+ build_configurations.each do |build_configuration|
90
+ build_configuration.build_settings["GCC_INSTRUMENT_PROGRAM_FLOW_ARCS"] = "YES"
91
+ build_configuration.build_settings["GCC_GENERATE_TEST_COVERAGE_FILES"] = "YES"
92
+ end
58
93
  end
59
94
 
60
95
  end