slather 0.0.2 → 0.0.3

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
- NTRhNzdkNDJiNDE5MjJkZjdiMzJmMTA2ZTVjNDA2MTRiZjgzNjZmMQ==
4
+ MjE0ZGEwODBjMDJiM2QyY2IyMDhhOWU2M2Y3ZTAyYjljZjdhNDk0OQ==
5
5
  data.tar.gz: !binary |-
6
- NjcyYzEzNGE0YWU0M2YzZTk1ZDk5NTc4OTdkZTQxZjY3YjAxYWJhMA==
6
+ MzFlYjM1YzliYzk4MWJlZWExYjYzZDQyMzM5YzJhNWQ0NDZlODc0OA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- Nzk1ZGUyYWIxOGJlMWE1NWMwZTUwZDA3NDY4ZjI0MjA5OWYwYTU0M2E4OGU1
10
- MDU0MDhlNTYyNDkyYTg5NjRjMmU2ZjJiYTBmMmFhNDg0OTA5NTYyMWU2YzA0
11
- ZmUzZDY2YzkxZGI2NzUwNjIxYjU3Y2RiODFiZDc4ODk0MmM2NmQ=
9
+ Yzg0NmNkYzY1MmUwZWExZTRkMjA0ODI2NWE1NWM4YWEwZjliYWUwZGUwZDhm
10
+ NDg0NGE0YmZiY2NjODI3ODQ3ZjcwNDQzMWJlY2QyZWJjOGE5Mzc3NWE3NWQ2
11
+ ZDZiYmJiOWNlMGZhNmJmMDhlNmJiNzMyYjU5ODUwNTY4NTczZmU=
12
12
  data.tar.gz: !binary |-
13
- YjQ4MDgxMTZmNmQ2OTJiYjgyZDhkNzRmYWFiYTI5MGY1MDk3YjIwYmM0OThl
14
- YTAwYjk1NmNiMmJlNTNkYjM3Yjk5NDlmNzA4NDMwZjNiNWMxNWVkODFhZjFi
15
- ZTg0ODgxOGQ1MjZhYTE5YTNjZWIxYzI5YzExMjg1MTVmYzYwMWU=
13
+ YTIyZThhMWFjY2VhMDA4NzUwOWE4YmI0YzFhN2Q2YTFkNDc2MzQ0NDQzMTVl
14
+ ZWQ5MjIyYTVkZTc5ODBhMDg4YTIwN2Y2ZDQ2OWE5YjZiZDA1MTUxYmE3OGU3
15
+ NTExNjBiZTFjYWY2ZDJjMDRkYTc2MWVkNjdkNmU5OGVmOGEwZTE=
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,17 +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
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
9
71
 
10
- def execute
11
- puts "slathering..."
12
- project = Slather::Project.open(xcodeproj_path)
13
- project.post_to_coveralls
14
- puts "done!"
15
72
  end
16
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
17
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,15 +3,16 @@ 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 = Dir["#{project.main_group.real_path}/**/#{base_filename}.m"].first
15
16
  path && Pathname(path)
16
17
  end
17
18
  end
@@ -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,47 +1,95 @@
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
 
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
16
+
8
17
  def derived_data_dir
9
18
  File.expand_path('~') + "/Library/Developer/Xcode/DerivedData/"
10
19
  end
11
20
  private :derived_data_dir
12
21
 
22
+ def build_directory
23
+ @build_directory || derived_data_dir
24
+ end
25
+
13
26
  def coverage_files
14
- Dir["#{derived_data_dir}/**/*.gcno"].map do |file|
15
- coverage_file = Slather::CoverallsCoverageFile.new(file)
16
- coverage_file.project = self
17
- # If there's no source file for this gcno, or the gcno is old, it probably belongs to another project.
18
- if coverage_file.source_file_pathname
19
- stale_seconds_limit = 60
20
- if (Time.now - File.mtime(file) < stale_seconds_limit)
21
- next coverage_file
22
- else
23
- puts "Skipping #{file} -- older than #{stale_seconds_limit} seconds ago."
24
- end
25
- end
26
- 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
27
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
28
38
  end
29
39
  private :coverage_files
30
40
 
31
- def coveralls_coverage_data
32
- {
33
- :service_job_id => ENV['TRAVIS_JOB_ID'] || 27647662,
34
- :service_name => "travis-ci",
35
- :source_files => coverage_files.map(&:as_json)
36
- }.to_json
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
37
86
  end
38
- private :coveralls_coverage_data
39
87
 
40
- def post_to_coveralls
41
- f = File.open('coveralls_json_file', 'w+')
42
- f.write(coveralls_coverage_data)
43
- `curl -s --form json_file=@#{f.path} https://coveralls.io/api/v1/jobs`
44
- FileUtils.rm(f)
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
45
93
  end
46
94
 
47
95
  end