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 +8 -8
- data/.gitignore +18 -0
- data/README.md +49 -10
- data/bin/slather +76 -10
- data/lib/slather.rb +5 -0
- data/lib/slather/coverage_file.rb +13 -6
- data/lib/slather/coverage_service/coveralls.rb +52 -0
- data/lib/slather/coverage_service/simple_output.rb +32 -0
- data/lib/slather/coveralls_coverage_file.rb +1 -1
- data/lib/slather/project.rb +70 -35
- data/lib/slather/version.rb +1 -1
- data/spec/fixtures/fixtures.xcodeproj/project.pbxproj +452 -0
- data/spec/fixtures/fixtures.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- data/spec/fixtures/fixtures/Supporting Files/fixtures-Prefix.pch +9 -0
- data/spec/fixtures/fixtures/fixtures.h +16 -0
- data/spec/fixtures/fixtures/fixtures.m +23 -0
- data/spec/fixtures/fixtures/more_files/peekaview.h +13 -0
- data/spec/fixtures/fixtures/more_files/peekaview.m +31 -0
- data/spec/fixtures/fixturesTests/Supporting Files/en.lproj/InfoPlist.strings +2 -0
- data/spec/fixtures/fixturesTests/Supporting Files/fixturesTests-Info.plist +22 -0
- data/spec/fixtures/fixturesTests/fixturesTests.m +36 -0
- data/spec/fixtures/fixturesTests/peekaviewTests.m +34 -0
- data/spec/slather/coverage_file_spec.rb +136 -0
- data/spec/slather/coverage_service/coveralls_spec.rb +66 -0
- data/spec/slather/coverage_service/simple_output_spec.rb +26 -0
- data/spec/slather/fixtures.gcno +0 -0
- data/spec/slather/project_spec.rb +219 -0
- data/spec/spec_helper.rb +10 -0
- metadata +39 -3
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NjczM2RkMDA4MTllMjlkNjJmZTkwOTQyOGZiMTkzMjY4ZTUwZDhiNg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MWIwNjIxZGNiNDYxNjM0ZjU0NmNjYmY4MGRiMjZiNjQ0YjkxZGI4Yg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
OWQ0NzczYjMzMTI3NTNlMjcwMWI5NTJiNTg2MDAxMWNjMWM3ODM5ZjUzZjAz
|
10
|
+
YjRmMDBhNmVjMzNhOTc4MzQwY2Q4ZTVmZTdmOGNhMTA4ZWVlNTQ1ODZiODEw
|
11
|
+
YjEzY2M0Y2FhOGY0NjRiNGMyZTFkNDBkYmIyMjFmNmQxOGY1MTg=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
3
|
+
Generate test coverage reports for Xcode projects. Apply tests liberally.
|
4
|
+
|
5
|
+
| [Parsimmon](https://github.com/ayanonagon/Parsimmon) | [](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
|
-
|
18
|
+
## Usage
|
16
19
|
|
17
|
-
|
20
|
+
Setup your project for test coverage:
|
18
21
|
|
19
|
-
|
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
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
3
|
+
require 'yaml'
|
4
|
+
require File.join(File.dirname(__FILE__), '../lib/slather')
|
5
5
|
|
6
6
|
Clamp do
|
7
7
|
|
8
|
-
|
9
|
-
|
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
|
-
|
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
|
-
|
15
|
-
|
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
|
28
|
-
source_file_pathname.relative_path_from(
|
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
|
data/lib/slather/project.rb
CHANGED
@@ -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 =
|
23
|
-
|
24
|
-
|
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
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
}
|
44
|
-
end
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|