alcove 0.1.0 → 0.2.0.pre.alpha.pre.36

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. checksums.yaml +13 -5
  2. data/README.md +21 -3
  3. data/bin/alcove +107 -44
  4. data/lib/alcove.rb +90 -102
  5. data/lib/alcove/version.rb +1 -1
  6. metadata +27 -13
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c3a24347defb0bd648d7c15cd13d754590615977
4
- data.tar.gz: a7530953e48cfc71bde431f94a235b3a17407293
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZmM2NTQyZDc5OGEyNGY5YjQ5YTFkMWRjODc1OTdlODg2MWJjOWMxZA==
5
+ data.tar.gz: !binary |-
6
+ NmMyYzI1NTM1ZmE0NDFkZDg1YjQ5M2QwMmRmMWYxNjI5NTJlY2U1OA==
5
7
  SHA512:
6
- metadata.gz: e74cccadf0a4dcd053843c3d5271c27e11c1823d91499d855c86981ea76b9c90c55490fd358522866b5c8c39ba54ca3a0f9542ee9827d71234e3be664da9f1a6
7
- data.tar.gz: a95b48ef3a9ccd19fabda7e85c88fb3f47c2a2602bf34b5483ca6bef751b9859f9d7e36060a826bbe6bca5d3d9ea045bc0382845ac72f3664566c2c8869106c8
8
+ metadata.gz: !binary |-
9
+ ZDM3YjY3Yjg1MzNlNTllYzMwMTY5N2Q0YTc4NGEyM2VhYjk4Yzg0NmFkNGVm
10
+ ZWEzMmMzNDY4YTJjZDEzZWQ2M2E2ZjAzMWU2MTc2MGJhNjk5ZjEyYjY2NzZl
11
+ NGM1ZmRhZWU3NGEyYmI4N2NmZDYzOGYwMjMxMjI5NThjNzgyNDc=
12
+ data.tar.gz: !binary |-
13
+ ZjVmOTRiN2JmOTE5M2ExMjY1NWM2ZjZlZmI1NTA1YzdhY2I3MWY0YTQyYTll
14
+ NTE1YTA4M2NmMmE5NjI2YmQ4OWVhNDI4MDUwMWE2ZmFjYzFhMDgyNmEwYTEw
15
+ MzVkNWJjYzQ5ZWIzYTE0ODk3MWZhNzM4MGVjMzExMmMxZDk5Y2Q=
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # Alcove [![Build Status](https://travis-ci.org/ioveracker/Alcove.svg?branch=master)](https://travis-ci.org/ioveracker/Alcove)
1
+ # Alcove [![Gem Version](https://badge.fury.io/rb/alcove.svg)](http://badge.fury.io/rb/alcove) [![Build Status](https://travis-ci.org/ioveracker/Alcove.svg?branch=master)](https://travis-ci.org/ioveracker/Alcove) [![Code Climate](https://codeclimate.com/github/ioveracker/Alcove/badges/gpa.svg)](https://codeclimate.com/github/ioveracker/Alcove)
2
2
  Painless code coverage reporting for Objective-C projects. Most of the heavy lifting is done by the venerable lcov. Alcove simply searches the nooks and crannies to collect the data needed to generate the report and ties everything together for you. Best of all, it's a gem with minimal depedencies, so installation is quick and painless.
3
3
 
4
4
  ## Installation
5
5
 
6
- $ sudo gem install alcove
6
+ $ gem install alcove
7
7
 
8
8
  If you don't have it already, you'll also need to install lcov.
9
9
 
@@ -16,7 +16,8 @@ If you don't have it already, you'll also need to install lcov.
16
16
  $ sudo port install lcov
17
17
 
18
18
  ## Xcode Project Configuration
19
- If you haven't already, open your project in Xcode and update your main target to Generate Test Coverage Files and Instrument Program Flow *for Debug configuration only*).
19
+ If you haven't already, open your project in Xcode and update your non-test targets to Generate Test Coverage Files and Instrument Program Flow (*for Debug configuration only*).
20
+ ![Xcode](http://i.imgur.com/xdcg4er.png?1)
20
21
 
21
22
  ## Generating Reports
22
23
  Now that you have the prerequisites out of the way, you can generate a report. Make sure you've recently executed your tests, then:
@@ -24,3 +25,20 @@ Now that you have the prerequisites out of the way, you can generate a report.
24
25
  alcove --product-name <your-product-name>
25
26
 
26
27
  Be sure to check out the --help for additional options for fine-tuning your report.
28
+
29
+ ## Options
30
+
31
+ ### --output-directory
32
+ Specify this option to change the output directory for the report. Any intermediate paths will be created.
33
+
34
+ ### --product-name
35
+ The product name specified in your Xcode project.
36
+
37
+ ### --remove-filter
38
+ A list of filters to use when gathering files for the report. Use this if you want to exclude certain files from the report. For example: `alcove --product-name SampleProduct --remove-filter *.h,main.m` will exclude header files and the main.m file from the report.
39
+
40
+ ### --search-directory
41
+ Use this option to specify the directory to be searched for your product. Alcove plays nicely with the the structure on your development machine, as well as on an Xcode Server, but if you have some funky output directory for your build, you can specify its parent here.
42
+
43
+ ## Attribution
44
+ Shoutout to [@NateBank](https://github.com/NateBank) for the [name suggestion](https://www.youtube.com/watch?v=j1Q-a5zCmhc).
data/bin/alcove CHANGED
@@ -7,62 +7,125 @@ require 'optparse'
7
7
 
8
8
  options = OpenStruct.new
9
9
  OptionParser.new do |opts|
10
- opts.banner = "Usage: alcove --product-name <product-name> [options]"
10
+ opts.banner = "Usage: alcove --product-name <product-name> [options]"
11
11
 
12
- opts.on('-h', '--help', 'Displays the help screen') do
13
- puts opts
14
- exit
15
- end
12
+ opts.on("-h", "--help", "Displays the help screen") do
13
+ puts opts
14
+ exit
15
+ end
16
16
 
17
- options.output_directory = 'alcove-report'
18
- opts.on('-o', '--output-directory DIR', 'Place report in DIR instead of default') do |o|
19
- options.output_directory = o
20
- end
17
+ options.output_directory = "alcove-report"
18
+ opts.on("-o", "--output-directory DIR", "Place report in DIR instead of default") do |o|
19
+ options.output_directory = o
20
+ end
21
21
 
22
- options.product_name = ''
23
- opts.on('-p', '--product-name NAME', 'The name of your product') do |p|
24
- options.product_name = p
25
- end
22
+ opts.on("--percent-file", "Generate a file containing coverage percent") do |p|
23
+ options.generate_percent_file = true
24
+ end
26
25
 
27
- options.remove_filter = []
28
- opts.on('-r', '--remove-filter X,Y,Z...', ::Array, 'A list of filters (e.g. *.h,main.m)') do |r|
29
- options.remove_filter = r
30
- end
26
+ options.product_name = ""
27
+ opts.on("-p", "--product-name NAME", "The name of your product") do |p|
28
+ options.product_name = p
29
+ end
31
30
 
32
- opts.on('-s', '--search-directory DIR', 'Search in DIR for coverage files') do |s|
33
- options.search_directory = s
34
- end
31
+ options.remove_filter = []
32
+ opts.on("-r", "--remove-filter X,Y,Z...", ::Array, "A list of filters (e.g. *.h,main.m)") do |r|
33
+ options.remove_filter = r
34
+ end
35
35
 
36
- options.verbose = false
37
- opts.on('-v', '--verbose', 'Output additional information') do
38
- options.verbose = true
39
- end
36
+ opts.on("-s", "--search-directory DIR", "Search in DIR for coverage files") do |s|
37
+ options.search_directory = s
38
+ end
40
39
 
41
- opts.on_tail('--version', 'Show version') do
42
- puts Alcove::VERSION
43
- exit
44
- end
40
+ options.verbose = false
41
+ opts.on("-v", "--verbose", "Output additional information") do
42
+ options.verbose = true
43
+ end
45
44
 
46
- begin
47
- opts.parse!(ARGV)
48
- if options.product_name.length == 0
49
- puts '--product-name is required'.yellow
50
- puts opts
51
- exit(1)
52
- end
53
- rescue OptionParser::InvalidOption => e
54
- puts e
55
- puts opts
56
- exit(1)
45
+ opts.on_tail("--version", "Show version") do
46
+ puts Alcove::VERSION
47
+ exit
48
+ end
49
+
50
+ begin
51
+ opts.parse!(ARGV)
52
+ if options.product_name.length == 0
53
+ STDERR.puts "--product-name is required".yellow
54
+ puts opts
55
+ exit(1)
57
56
  end
57
+ rescue OptionParser::InvalidOption => e
58
+ puts e
59
+ puts opts
60
+ exit(1)
61
+ end
62
+ end
63
+
64
+ def exit_with_code(code)
65
+ FileUtils.rm_rf(Alcove::TEMP_DIR)
66
+ exit(code)
58
67
  end
59
68
 
60
69
  alcoveOptions = OpenStruct.new
61
- alcoveOptions.output_directory = options.output_directory
62
- alcoveOptions.product_name = options.product_name
63
- alcoveOptions.remove_filter = options.remove_filter
64
- alcoveOptions.search_directory = options.search_directory
65
70
  alcoveOptions.verbose = options.verbose
66
71
 
67
72
  alcove = Alcove.new(alcoveOptions)
68
- alcove.generate_report
73
+ puts " 🔍 Generating report..."
74
+
75
+ FileUtils.rm_rf(Alcove::TEMP_DIR)
76
+ FileUtils.mkdir(Alcove::TEMP_DIR)
77
+
78
+ if options.search_directory
79
+ search_directory = options.search_directory
80
+ else
81
+ search_directory = alcove.get_search_directory
82
+ end
83
+ alcove.copy_input_files_to_temp(search_directory, options.product_name)
84
+
85
+ gi_filename_absolute = File.join(Alcove::TEMP_DIR, "alcove-info.temp")
86
+ gen_success = alcove.gen_info_files(gi_filename_absolute)
87
+ if gen_success
88
+ puts " ✅ geninfo successful".green if options.verbose
89
+ else
90
+ STDERR.puts " 🚫 geninfo failed!".red
91
+ exit_with_code(1)
92
+ end
93
+
94
+ lcov_filename_absolute = File.join(Alcove::TEMP_DIR, "alcove-lcov.info")
95
+ lcov_success = alcove.lcov(gi_filename_absolute, options.remove_filter, lcov_filename_absolute)
96
+ if lcov_success
97
+ puts " ✅ lcov successful".green if options.verbose
98
+ else
99
+ STDERR.puts " 🚫 lcov failed!".red
100
+ exit_with_code(1)
101
+ end
102
+
103
+ genhtml_success, coverage_percent = alcove.genhtml(lcov_filename_absolute, options.output_directory)
104
+ if genhtml_success
105
+ puts "" if options.verbose
106
+ puts " ✅ Successfully generated report".green
107
+
108
+ coverage_string = " 📊 Line coverage: #{coverage_percent}%"
109
+ if coverage_percent < 50
110
+ puts coverage_string.red
111
+ elsif coverage_percent < 85
112
+ puts coverage_string.yellow
113
+ else
114
+ puts coverage_string.green
115
+ end
116
+
117
+ puts " 🍻 Open #{options.output_directory}/index.html to view the report"
118
+
119
+ else
120
+ STDERR.puts " 🚫 genhtml failed!".red
121
+ exit_with_code(1)
122
+ end
123
+
124
+ if options.generate_percent_file
125
+ File.open("#{options.output_directory}/alcove-percent.txt", 'w') {|f|
126
+ f.write(coverage_percent)
127
+ }
128
+
129
+ end
130
+
131
+ exit_with_code(0)
@@ -4,110 +4,98 @@ require 'find'
4
4
  require 'fileutils'
5
5
  require 'ostruct'
6
6
  require 'optparse'
7
+ require 'open3'
7
8
 
8
9
  class Alcove
9
- def initialize(options)
10
- @output_directory = options.output_directory
11
- @product_name = options.product_name
12
- @remove_filter = options.remove_filter
13
- @verbose = options.verbose
14
- @search_directory = options.search_directory
10
+ TEMP_DIR = "alcove-temp"
11
+ # Public: Initializes a new instance.
12
+ def initialize(options)
13
+ @verbose = options.verbose
14
+ end
15
+
16
+ # Public: Determines the directory to use when searching for .gcno and .gcda
17
+ # files.
18
+ #
19
+ # Returns the directory to search.
20
+ def get_search_directory
21
+ if ENV["XCS_SOURCE_DIR"]
22
+ puts " Xcode Server found." if @verbose
23
+ ENV["XCS_SOURCE_DIR"].sub("Source", "DerivedData")
24
+ else
25
+ puts " Development machine found." if @verbose
26
+ File.join(Etc.getpwuid.dir, "/Library/Developer/Xcode/DerivedData")
15
27
  end
16
-
17
- def generate_report
18
- temp_dir = 'alcove-temp'
19
- # geninfo parameters
20
- gi_filename = 'alcove-info.temp'
21
- gi_filename_absolute = File.join(temp_dir, gi_filename)
22
- # lcov parameters
23
- lcov_filename = 'alcove-lcov.info'
24
- lcov_filename_absolute = File.join(temp_dir, lcov_filename)
25
- lcov_system_removals = ['*iPhoneSimulator*']
26
-
27
- puts ' 🔍 Generating report...'
28
-
29
- FileUtils.rm_rf(temp_dir)
30
- FileUtils.mkdir(temp_dir)
31
-
32
- puts ' 📦 Gathering .gcno and .gcda files...' if @verbose
33
- copy_input_files(@product_name, temp_dir)
34
-
35
- gen_success = gen_info_files(gi_filename_absolute, temp_dir)
36
- if gen_success
37
- puts ' ✅ geninfo successful'.green if @verbose
38
- else
39
- puts ' 🚫 geninfo failed!'.red
40
- FileUtils.rm_rf(temp_dir)
41
- exit(1)
42
- end
43
-
44
- lcov_removals = lcov_system_removals + @remove_filter
45
- lcov_success = lcov(gi_filename_absolute, lcov_removals, lcov_filename_absolute)
46
- if lcov_success
47
- puts ' ✅ lcov successful'.green if @verbose
48
- else
49
- puts ' 🚫 lcov failed!'.red
50
- FileUtils.rm_rf(temp_dir)
51
- exit(1)
52
- end
53
-
54
- genhtml_success = genhtml(@output_directory, lcov_filename_absolute)
55
- if genhtml_success
56
- puts '' if @verbose
57
- puts ' ✅ Successfully generated report'.green
58
- puts " 🍻 Open #{@output_directory}/index.html to view the report"
59
- else
60
- puts ' 🚫 genhtml failed!'.red
61
- FileUtils.rm_rf(temp_dir)
62
- exit(1)
63
- end
64
-
65
- FileUtils.rm_rf(temp_dir)
66
- end
67
-
68
- private
69
-
70
- def copy_input_files(product_name, temp_dir)
71
- derived_data = ''
72
- if @search_directory
73
- puts " Search directory provided." if @verbose
74
- derived_data = @search_directory
75
- elsif ENV['XCS_SOURCE_DIR']
76
- puts " Xcode Server found." if @verbose
77
- derived_data = ENV['XCS_SOURCE_DIR'].sub('Source', 'DerivedData')
78
- else
79
- puts " Development machine found." if @verbose
80
- derived_data = File.join(Etc.getpwuid.dir, "/Library/Developer/Xcode/DerivedData")
81
- end
82
-
83
- puts " Searching in #{derived_data}..." if @verbose
84
- Find.find(derived_data) do |path|
85
- if path.match(/#{product_name}.*\.gcda\Z/) || path.match(/#{product_name}.*\.gcno\Z/)
86
- puts " 👍 .#{path.sub(derived_data, "")}".green if @verbose
87
- FileUtils.cp(path, "#{temp_dir}/")
88
- end
89
- end
90
- end
91
-
92
- def gen_info_files(filename, temp_dir)
93
- absolute_temp_dir = File.join(Dir.pwd, temp_dir)
94
- gen_info_cmd = "geninfo #{absolute_temp_dir}/*.gcno --output-filename #{filename}"
95
- gen_info_cmd += ' --quiet' unless @verbose
96
- return system gen_info_cmd
97
- end
98
-
99
- def lcov(info_filename, filenames_to_remove, lcov_file)
100
- all_removals = filenames_to_remove.map { |i| '"' + i.to_s + '"' }.join(" ")
101
- lcov_cmd = "lcov --remove #{info_filename} #{all_removals} > #{lcov_file}"
102
- lcov_cmd += ' --quiet' unless @verbose
103
- return system lcov_cmd
104
- end
105
-
106
-
107
- def genhtml(output_directory, lcov_filename)
108
- FileUtils.mkpath(output_directory)
109
- genhtml_cmd = "genhtml --no-function-coverage --no-branch-coverage --output-directory #{output_directory} #{lcov_filename}"
110
- genhtml_cmd += ' --quiet' unless @verbose
111
- return system genhtml_cmd
28
+ end
29
+
30
+ # Public: Searches for the .gcno and .gcda files required to generate the
31
+ # report and copies them into the temp directory.
32
+ #
33
+ # search_directory - The directory to search for .gcno and .gcda files.
34
+ # product_name - The product name (${PRODUCT_NAME}) from the Xcode
35
+ # project, which will be used to determine what .gcno and
36
+ # .gcda files to copy.
37
+ #
38
+ # Returns nothing.
39
+ def copy_input_files_to_temp(search_directory, product_name)
40
+ puts " 📦 Gathering .gcno and .gcda files..." if @verbose
41
+ puts " Searching in #{search_directory}..." if @verbose
42
+ Find.find(search_directory) do |path|
43
+ if path.match(/#{product_name}.*\.gcda\Z/) || path.match(/#{product_name}.*\.gcno\Z/)
44
+ puts " 👍 .#{path.sub(search_directory, "")}".green if @verbose
45
+ FileUtils.cp(path, "#{Alcove::TEMP_DIR}/")
46
+ end
112
47
  end
48
+ end
49
+
50
+ # Public: Calls the geninfo command to generate information files for
51
+ # lcov to process.
52
+ #
53
+ # filename - The name of the file to be created by geninfo.
54
+ #
55
+ # Returns the result of the geninfo command.
56
+ def gen_info_files(filename)
57
+ absolute_temp_dir = File.join(Dir.pwd, Alcove::TEMP_DIR)
58
+ gen_info_cmd = "geninfo #{absolute_temp_dir}/*.gcno --output-filename #{filename}"
59
+ gen_info_cmd << " --quiet" unless @verbose
60
+ system gen_info_cmd
61
+ end
62
+
63
+ # Public: Calls the lcov command to generate coverage information files.
64
+ #
65
+ # info_filename - The name of the file generated by geninfo
66
+ # filenames_to_remove - An array of filters to remove from the report.
67
+ # lcov_file - The file in which lcov will place the generated info.
68
+ #
69
+ # Returns the result of the lcov command.
70
+ def lcov(info_filename, filenames_to_remove, lcov_file)
71
+ all_removals = filenames_to_remove.map { |i| "\"#{i.to_s}\"" }.join(" ")
72
+ lcov_cmd = "lcov --remove #{info_filename} #{all_removals} > #{lcov_file}"
73
+ lcov_cmd << " --quiet" unless @verbose
74
+ system lcov_cmd
75
+ end
76
+
77
+ # Public: Calls the genhtml command to generate an HTML report from the
78
+ # lcov information file.
79
+ #
80
+ # lcov_file_path - The path to the file generated by lcov.
81
+ # output_directory - The directory where output files should be placed.
82
+ #
83
+ # Returns the result of the genhtml command.
84
+ def genhtml(lcov_file_path, output_directory)
85
+ FileUtils.mkpath(output_directory)
86
+ genhtml_cmd = "genhtml --no-function-coverage --no-branch-coverage --output-directory #{output_directory} #{lcov_file_path}"
87
+
88
+ stdout, stderr, exit_status = Open3.capture3(genhtml_cmd)
89
+ puts stdout if @verbose
90
+ summary_string = extract_percent_from_summary(stdout)
91
+
92
+ return exit_status.success?, summary_string
93
+ end
94
+
95
+ # Public: Extracts the percentage from the lcov summary string.
96
+ #
97
+ # summary - The summary string output by lcov or genhtml
98
+ def extract_percent_from_summary(summary)
99
+ summary[/lines.*: (.*)%/, 1].to_f
100
+ end
113
101
  end
@@ -1,3 +1,3 @@
1
1
  class Alcove
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alcove
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0.pre.alpha.pre.36
5
5
  platform: ruby
6
6
  authors:
7
7
  - Isaac Overacker
@@ -28,30 +28,44 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ! '>='
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ! '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ! '>='
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ! '>='
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '5.5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '5.5'
55
69
  description: Painless code coverage reporting for Xcode projects written in Objective-C.
56
70
  email: isaac@overacker.me
57
71
  executables:
@@ -59,11 +73,11 @@ executables:
59
73
  extensions: []
60
74
  extra_rdoc_files: []
61
75
  files:
62
- - lib/alcove/version.rb
63
- - lib/alcove.rb
64
- - bin/alcove
65
- - README.md
66
76
  - LICENSE
77
+ - README.md
78
+ - bin/alcove
79
+ - lib/alcove.rb
80
+ - lib/alcove/version.rb
67
81
  homepage: https://github.com/ioveracker/alcove
68
82
  licenses:
69
83
  - MIT
@@ -74,17 +88,17 @@ require_paths:
74
88
  - lib
75
89
  required_ruby_version: !ruby/object:Gem::Requirement
76
90
  requirements:
77
- - - '>='
91
+ - - ! '>='
78
92
  - !ruby/object:Gem::Version
79
93
  version: '0'
80
94
  required_rubygems_version: !ruby/object:Gem::Requirement
81
95
  requirements:
82
- - - '>='
96
+ - - ! '>'
83
97
  - !ruby/object:Gem::Version
84
- version: '0'
98
+ version: 1.3.1
85
99
  requirements: []
86
100
  rubyforge_project:
87
- rubygems_version: 2.0.14
101
+ rubygems_version: 2.4.5
88
102
  signing_key:
89
103
  specification_version: 4
90
104
  summary: Painless code coverage reporting for Objective-C.