slather 0.0.23 → 0.0.31

Sign up to get free protection for your applications and to get access to all the features.
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