xcov 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3e237c6cf2adb117d8ef52a834dda469ac74413d
4
- data.tar.gz: 1f41de3cd2510bb12a3acb81675d99e31f2bb426
3
+ metadata.gz: e3471895be03f8b9d1f02805ac5a0e72775d67c9
4
+ data.tar.gz: fce99c62ab472f32c6db79659cbfc473aa5afa2d
5
5
  SHA512:
6
- metadata.gz: a28236541164500f2a6db7110a2ee0ec347f7f7fdf66a46e57754f019dcf2226044740266e97c04fa573b7e3a3ed93c023e251eec7cf8cf9c80fd7c1e304ea53
7
- data.tar.gz: 9bf9d031e572383bce6d7ffd53ae8a28819dd422bf8fde19d64b960892acb12d4932990b43546ee4076dde64eea888eb551397ac5a616e2bb27a89291095451f
6
+ metadata.gz: dca0119f6dfe20839137d471738a7a5d1ca275a49a89c05805db8daf6e8d2368f1747546d3c6f866395e07a9770421b2047f2ea44aa2b4a79459a536d131ead8
7
+ data.tar.gz: 944c9806c4f941d67c59463a3119ca50604673346fa68237437ad25a879015ea779a1b15c19e3dfb3aba14185146d0e3d7257c0e2a1b59510cce15d2a612d37f
data/README.md CHANGED
@@ -13,9 +13,10 @@ sudo gem install xcov
13
13
  ```
14
14
 
15
15
  ## Features
16
- * Built on top of [Fastlane](https://fastlane.tools), you can easily plug it on to your CI environment.
16
+ * Built on top of [fastlane](https://fastlane.tools), you can easily plug it on to your CI environment.
17
17
  * Blacklisting of those files which coverage you want to ignore.
18
18
  * Minimum acceptable coverage percentage.
19
+ * Compatible with [Coveralls](https://coveralls.io).
19
20
  * Nice HTML reports.
20
21
 
21
22
  ![/assets_readme/report.png](/assets_readme/report.png)
@@ -34,7 +35,7 @@ In order to make *xcov* run you must:
34
35
  ![/assets_readme/gather_coverage.png](/assets_readme/gather_coverage.png)
35
36
 
36
37
  ## Usage
37
- *xcov* analyzes the `.xccoverage` files created after running your tests therefore, before executing xcov, you need to run your tests with either `Xcode`, `xcodebuild` or [scan](https://github.com/fastlane/scan). Once completed, obtain your coverage report by providing a few parameters:
38
+ *xcov* analyzes the `.xccoverage` files created after running your tests therefore, before executing xcov, you need to run your tests with either `Xcode`, `xcodebuild` or [scan](https://github.com/fastlane/fastlane/tree/master/scan). Once completed, obtain your coverage report by providing a few parameters:
38
39
  ```
39
40
  xcov -w LystSDK.xcworkspace -s LystSDK -o xcov_output
40
41
  ```
@@ -47,7 +48,7 @@ xcov -w LystSDK.xcworkspace -s LystSDK -o xcov_output
47
48
  * `--output_directory` `-o`: Path for the output folder where the report files will be saved.
48
49
  * `--source_directory` `-r`: The path to project's root directory.
49
50
  * `--derived_data_path` `-j`: Path of your project `Derived Data` folder (optional).
50
- * `--minimum_coverage_percentage` `-m`: Raise exception if overall coverage percentage is under this value (ie. 75).
51
+ * `--minimum_coverage_percentage` `-m`: Raise exception if overall coverage percentage is under this value (ie. 75.0).
51
52
  * `--include_test_targets`: Enables coverage reports for `.xctest` targets.
52
53
  * `--ignore_file_path` `-x`: Relative or absolute path to the file containing the list of ignored files.
53
54
  * `--exclude_targets`: Comma separated list of targets to exclude from coverage report.
@@ -59,6 +60,9 @@ xcov -w LystSDK.xcworkspace -s LystSDK -o xcov_output
59
60
  * `--markdown_report`: Enables the creation of a markdown report (optional).
60
61
  * `--skip_slack`: Add this flag to avoid publishing results on Slack (optional).
61
62
  * `--only_project_targets`: Display the coverage only for main project targets (e.g. skip Pods targets).
63
+ * `--coveralls_service_name`: Name of the CI service compatible with Coveralls. i.e. travis-ci. This option must be defined along with coveralls_service_job_id.
64
+ * `--coveralls_service_job_id`: Name of the current job running on a CI service compatible with Coveralls. This option must be defined along with coveralls_service_name.
65
+ * `--coveralls_repo_token`: Repository token to be used by integrations not compatible with Coveralls.
62
66
 
63
67
  _**Note:** All paths you provide should be absolute and unescaped_
64
68
 
data/lib/xcov-core.rb CHANGED
@@ -20,27 +20,26 @@ module Xcov
20
20
  JSON.parse(output_file)
21
21
  end
22
22
 
23
- def self.execute_command (command, description)
24
- FastlaneCore::CommandExecutor
25
- .execute(
26
- command: command,
27
- print_all: true,
28
- print_command: true,
29
- prefix: description,
30
- loading: "Loading...",
31
- error: proc do |error_output|
32
- begin
33
- Xcov::ErrorHandler.handle_error(error_output)
34
- rescue => ex
35
- Xcov::SlackPoster.new.run({
36
- build_errors: 1
37
- })
38
- raise ex
39
- end
40
- end
41
- )
23
+ def self.execute_command(command, description)
24
+ FastlaneCore::CommandExecutor.execute(
25
+ command: command,
26
+ print_all: true,
27
+ print_command: true,
28
+ prefix: description,
29
+ loading: "Loading...",
30
+ error: proc do |error_output|
31
+ begin
32
+ Xcov::ErrorHandler.handle_error(error_output)
33
+ rescue => ex
34
+ Xcov::SlackPoster.new.run({
35
+ build_errors: 1
36
+ })
37
+ raise ex
38
+ end
39
+ end
40
+ )
42
41
  end
43
-
42
+
44
43
  end
45
44
 
46
45
  end
data/lib/xcov.rb CHANGED
@@ -2,9 +2,9 @@ require 'json'
2
2
  require 'xcov/version'
3
3
  require 'xcov/manager'
4
4
  require 'xcov/options'
5
- require 'xcov/runner'
6
5
  require 'xcov/ignore_handler'
7
6
  require 'xcov/error_handler'
7
+ require 'xcov/coveralls_handler'
8
8
  require 'xcov/slack_poster'
9
9
  require 'xcov/model/base'
10
10
  require 'xcov/model/report'
@@ -38,7 +38,7 @@ module Xcov
38
38
  c.description = Xcov::DESCRIPTION
39
39
  c.action do |_args, options|
40
40
  config = FastlaneCore::Configuration.create(Xcov::Options.available_options, convert_options(options))
41
- Xcov::Manager.new.work(config)
41
+ Xcov::Manager.new(config).run()
42
42
  end
43
43
  end
44
44
 
@@ -0,0 +1,98 @@
1
+ require "tempfile"
2
+
3
+ module Xcov
4
+ class CoverallsHandler
5
+
6
+ class << self
7
+
8
+ def submit(report)
9
+ coveralls_json_path = convert_and_store_coveralls_json(report)
10
+ perform_request(coveralls_json_path)
11
+ end
12
+
13
+ private
14
+
15
+ def convert_and_store_coveralls_json(report)
16
+ root_path = `git rev-parse --show-toplevel`
17
+ root_path.delete!("\n")
18
+ root_path << '/'
19
+
20
+ # Iterate through targets
21
+ source_files = []
22
+ report.targets.each do |target|
23
+ # Iterate through target files
24
+ target.files.each do |file|
25
+ next if file.ignored
26
+
27
+ # Iterate through file lines
28
+ lines = []
29
+ file.lines.each do |line|
30
+ lines << line.execution_count if line.executable
31
+ lines << nil unless line.executable
32
+ end
33
+
34
+ relative_path = file.location
35
+ relative_path.slice!(root_path)
36
+ source_files << {
37
+ name: relative_path,
38
+ source_digest: digest_for_file(relative_path),
39
+ coverage: lines
40
+ }
41
+ end
42
+ end
43
+
44
+ json = {
45
+ service_job_id: Xcov.config[:coveralls_service_job_id],
46
+ service_name: Xcov.config[:coveralls_service_name],
47
+ repo_token: Xcov.config[:coveralls_repo_token],
48
+ source_files: source_files
49
+ }
50
+
51
+ require "json"
52
+
53
+ # Persist
54
+ coveralls_json_file = Tempfile.new("coveralls_report.json")
55
+ File.open(coveralls_json_file.path, "wb") do |file|
56
+ file.puts JSON.pretty_generate(json)
57
+ file.close
58
+ end
59
+
60
+ # Return path
61
+ return coveralls_json_file.path
62
+ end
63
+
64
+ def perform_request(coveralls_json_path)
65
+ require 'net/http/post/multipart'
66
+
67
+ # Build request
68
+ url = URI.parse("https://coveralls.io/api/v1/jobs")
69
+ UI.message "Uploading coverage report to coveralls.io".yellow
70
+ request = Net::HTTP::Post::Multipart.new(
71
+ url.path,
72
+ "json_file" => UploadIO.new(File.new(coveralls_json_path), "text/plain", "coveralls_report.json")
73
+ )
74
+
75
+ # Perform request
76
+ http = Net::HTTP.new(url.host, url.port)
77
+ http.use_ssl = true
78
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
79
+ response = http.request(request)
80
+
81
+ if response.code == "200"
82
+ UI.message "Submitted report to coveralls.io successfully".green
83
+ else
84
+ UI.message "There was an error submitting the report to coveralls.io".red
85
+ UI.message response.body.red
86
+ end
87
+ end
88
+
89
+ def digest_for_file(file_path)
90
+ hash = `git hash-object #{file_path}`
91
+ hash.delete!("\n")
92
+ return hash
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+ end
@@ -3,36 +3,26 @@ module Xcov
3
3
  class ErrorHandler
4
4
 
5
5
  class << self
6
- # @param [String] The output of the errored build
7
- # This method should raise an exception in any case, as the return code indicated a failed build
6
+
8
7
  def handle_error(output)
9
- # The order of the handling below is import
10
8
  case output
11
- when /US\-ASCII/
12
- print "Your shell environment is not correctly configured"
13
- print "Instead of UTF-8 your shell uses US-ASCII"
14
- print "Please add the following to your '~/.bashrc':"
15
- print ""
16
- print " export LANG=en_US.UTF-8"
17
- print " export LANGUAGE=en_US.UTF-8"
18
- print " export LC_ALL=en_US.UTF-8"
19
- print ""
20
- print "You'll have to restart your shell session after updating the file."
21
- print "If you are using zshell or another shell, make sure to edit the correct bash file."
22
- print "For more information visit this stackoverflow answer:"
23
- print "https://stackoverflow.com/a/17031697/445598"
24
- when /CoverageNotFound/
9
+ when /XccoverageFileNotFound/
25
10
  print "Unable to find any .xccoverage file."
26
11
  print "Make sure you have enabled 'Gather code coverage' setting on your scheme settings."
27
12
  print "Alternatively you can provide the full path to your .xccoverage file."
28
- when /Executed/
29
- # this is *really* important:
30
- # we don't want to raise an exception here
31
- # as we handle this in runner.rb at a later point
32
- # after parsing the actual test results
33
- return
13
+ when /UnableToParseXccoverageFile/
14
+ print "There was an error converting the .xccoverage file to json."
15
+ when /CoverageUnderThreshold/
16
+ print "The build has been marked as failed because minimum overall coverage has not been reached."
17
+ when /UnableToMapJsonToXcovModel/
18
+ print "There was an error converting the json file to xcov's model objects."
34
19
  end
35
- raise "Error processing coverage file - see the log above".red
20
+ raise "Error creating your coverage report - see the log above".red
21
+ end
22
+
23
+ def handle_error_with_custom_message(error, custom_message)
24
+ print custom_message
25
+ handle_error error
36
26
  end
37
27
 
38
28
  private
@@ -40,6 +30,7 @@ module Xcov
40
30
  def print(text)
41
31
  UI.message text.red
42
32
  end
33
+
43
34
  end
44
35
 
45
36
  end
@@ -25,7 +25,7 @@ module Xcov
25
25
  # Ignore specific files
26
26
  filename = File.basename(path)
27
27
  return true if should_ignore_file(filename)
28
-
28
+
29
29
  # Also ignore the files from ignored folders
30
30
  relative = relative_path(path).downcase
31
31
  return @list.any? { |ignored_path| relative.start_with? ignored_path }
data/lib/xcov/manager.rb CHANGED
@@ -1,9 +1,16 @@
1
- require "fastlane_core"
1
+ require 'fastlane_core'
2
+ require 'pty'
3
+ require 'open3'
4
+ require 'fileutils'
5
+ require 'terminal-table'
6
+ require 'xcov-core'
7
+ require 'pathname'
8
+ require 'json'
2
9
 
3
10
  module Xcov
4
11
  class Manager
5
12
 
6
- def work(options)
13
+ def initialize(options)
7
14
  # Set command options
8
15
  Xcov.config = options
9
16
 
@@ -16,9 +23,125 @@ module Xcov
16
23
 
17
24
  # Print summary
18
25
  FastlaneCore::PrintTable.print_values(config: options, hide_keys: [:slack_url], title: "Summary for xcov #{Xcov::VERSION}")
26
+ end
19
27
 
28
+ def run
20
29
  # Run xcov
21
- Runner.new.run
30
+ json_report = parse_xccoverage
31
+ report = generate_xcov_report(json_report)
32
+ validate_report(report)
33
+ submit_to_coveralls(report)
34
+ end
35
+
36
+ private
37
+
38
+ def parse_xccoverage
39
+ # Find .xccoverage file
40
+ test_logs_path = derived_data_path + "Logs/Test/"
41
+ xccoverage_files = Dir["#{test_logs_path}*.xccoverage"].sort_by { |filename| File.mtime(filename) }.reverse
42
+
43
+ unless test_logs_path.directory? && !xccoverage_files.empty?
44
+ ErrorHandler.handle_error("XccoverageFileNotFound")
45
+ end
46
+
47
+ # Convert .xccoverage file to json
48
+ json_report = Xcov::Core::Parser.parse(xccoverage_files.first)
49
+ ErrorHandler.handle_error("UnableToParseXccoverageFile") if json_report.nil?
50
+
51
+ json_report
52
+ end
53
+
54
+ def generate_xcov_report(json_report)
55
+ # Create output path
56
+ output_path = Xcov.config[:output_directory]
57
+ FileUtils.mkdir_p(output_path)
58
+
59
+ # Convert report to xcov model objects
60
+ report = Report.map(json_report)
61
+
62
+ # Raise exception in case of failure
63
+ ErrorHandler.handle_error("UnableToMapJsonToXcovModel") if report.nil?
64
+
65
+ if Xcov.config[:html_report] then
66
+ resources_path = File.join(output_path, "resources")
67
+ FileUtils.mkdir_p(resources_path)
68
+
69
+ # Copy images to output resources folder
70
+ Dir[File.join(File.dirname(__FILE__), "../../assets/images/*")].each do |path|
71
+ FileUtils.cp_r(path, resources_path)
72
+ end
73
+
74
+ # Copy stylesheets to output resources folder
75
+ Dir[File.join(File.dirname(__FILE__), "../../assets/stylesheets/*")].each do |path|
76
+ FileUtils.cp_r(path, resources_path)
77
+ end
78
+
79
+ # Copy javascripts to output resources folder
80
+ Dir[File.join(File.dirname(__FILE__), "../../assets/javascripts/*")].each do |path|
81
+ FileUtils.cp_r(path, resources_path)
82
+ end
83
+
84
+ # Create HTML report
85
+ File.open(File.join(output_path, "index.html"), "wb") do |file|
86
+ file.puts report.html_value
87
+ end
88
+ end
89
+
90
+ # Create Markdown report
91
+ if Xcov.config[:markdown_report] then
92
+ File.open(File.join(output_path, "report.md"), "wb") do |file|
93
+ file.puts report.markdown_value
94
+ end
95
+ end
96
+
97
+ # Create JSON report
98
+ if Xcov.config[:json_report] then
99
+ File.open(File.join(output_path, "report.json"), "wb") do |file|
100
+ file.puts report.json_value.to_json
101
+ end
102
+ end
103
+
104
+ # Post result
105
+ SlackPoster.new.run(report)
106
+
107
+ # Print output
108
+ table_rows = []
109
+ report.targets.each do |target|
110
+ table_rows << [target.name, target.displayable_coverage]
111
+ end
112
+ puts Terminal::Table.new({
113
+ title: "xcov Coverage Report".green,
114
+ rows: table_rows
115
+ })
116
+ puts ""
117
+
118
+ report
119
+ end
120
+
121
+ def validate_report(report)
122
+ # Raise exception if overall coverage is under threshold
123
+ minimumPercentage = Xcov.config[:minimum_coverage_percentage] / 100
124
+ if minimumPercentage > report.coverage
125
+ error_message = "Actual Code Coverage (#{"%.2f%" % (report.coverage*100)}) below threshold of #{"%.2f%" % (minimumPercentage*100)}"
126
+ ErrorHandler.handle_error_with_custom_message("CoverageUnderThreshold", error_message)
127
+ end
128
+ end
129
+
130
+ def submit_to_coveralls(report)
131
+ if !Xcov.config[:coveralls_repo_token].nil? || !(Xcov.config[:coveralls_service_name].nil? && Xcov.config[:coveralls_service_job_id].nil?)
132
+ CoverallsHandler.submit(report)
133
+ end
134
+ end
135
+
136
+ # Auxiliar methods
137
+
138
+ def derived_data_path
139
+ # If DerivedData path was supplied, return
140
+ return Pathname.new(Xcov.config[:derived_data_path]) unless Xcov.config[:derived_data_path].nil?
141
+
142
+ # Otherwise check project file
143
+ product_builds_path = Pathname.new(Xcov.project.default_build_settings(key: "SYMROOT"))
144
+ return product_builds_path.parent.parent
22
145
  end
23
146
 
24
147
  end
@@ -70,4 +70,3 @@ module Xcov
70
70
 
71
71
  end
72
72
  end
73
-
@@ -3,7 +3,7 @@ require 'cgi'
3
3
  module Xcov
4
4
  class Function < Xcov::Base
5
5
 
6
- def initialize (name, coverage)
6
+ def initialize(name, coverage)
7
7
  @name = CGI::escapeHTML(name)
8
8
  @coverage = coverage
9
9
  @displayable_coverage = self.create_displayable_coverage
@@ -27,7 +27,7 @@ module Xcov
27
27
 
28
28
  # Class methods
29
29
 
30
- def self.map (dictionary)
30
+ def self.map(dictionary)
31
31
  Function.new(dictionary["name"], dictionary["coverage"])
32
32
  end
33
33
 
@@ -5,7 +5,7 @@ module Xcov
5
5
  attr_reader :executable
6
6
  attr_reader :ranges
7
7
 
8
- def initialize (execution_count, executable, ranges = nil)
8
+ def initialize(execution_count, executable, ranges = nil)
9
9
  @execution_count = execution_count
10
10
  @executable = executable
11
11
  @ranges = ranges
@@ -17,12 +17,12 @@ module Xcov
17
17
 
18
18
  # Class methods
19
19
 
20
- def self.map (dictionary)
20
+ def self.map(dictionary)
21
21
  ranges = map_ranges(dictionary["ranges"])
22
22
  Line.new(dictionary["executionCount"], dictionary["executable"], ranges)
23
23
  end
24
24
 
25
- def self.map_ranges (dictionaries)
25
+ def self.map_ranges(dictionaries)
26
26
  return nil if dictionaries.nil?
27
27
  dictionaries.map { |dictionary| Range.map(dictionary) }
28
28
  end
@@ -5,7 +5,7 @@ module Xcov
5
5
  attr_reader :location
6
6
  attr_reader :length
7
7
 
8
- def initialize (execution_count, location, length)
8
+ def initialize(execution_count, location, length)
9
9
  @execution_count = execution_count
10
10
  @location = location
11
11
  @length = length
@@ -13,7 +13,7 @@ module Xcov
13
13
 
14
14
  # Class methods
15
15
 
16
- def self.map (dictionary)
16
+ def self.map(dictionary)
17
17
  Range.new(
18
18
  dictionary["executionCount"],
19
19
  dictionary["location"],
@@ -7,7 +7,7 @@ module Xcov
7
7
  attr_accessor :summary
8
8
  attr_accessor :target_templates
9
9
 
10
- def initialize (targets)
10
+ def initialize(targets)
11
11
  @targets = targets
12
12
  @coverage = average_coverage(targets)
13
13
  @displayable_coverage = self.create_displayable_coverage
@@ -61,7 +61,7 @@ module Xcov
61
61
 
62
62
  # Class methods
63
63
 
64
- def self.map dictionary
64
+ def self.map(dictionary)
65
65
  targets = Report.filter_targets dictionary["targets"]
66
66
 
67
67
  # Create target objects
@@ -70,7 +70,7 @@ module Xcov
70
70
  Report.new(targets)
71
71
  end
72
72
 
73
- def self.filter_targets targets
73
+ def self.filter_targets(targets)
74
74
  filtered_targets = Array.new(targets)
75
75
  filtered_targets = filtered_targets.select { |target| !target["name"].include?(".xctest") } if !Xcov.config[:include_test_targets]
76
76
 
@@ -12,7 +12,7 @@ module Xcov
12
12
  attr_accessor :function_templates
13
13
  attr_accessor :lines
14
14
 
15
- def initialize (name, location, coverage, functions, lines = nil)
15
+ def initialize(name, location, coverage, functions, lines = nil)
16
16
  @name = CGI::escapeHTML(name)
17
17
  @location = CGI::escapeHTML(location)
18
18
  @coverage = coverage
@@ -74,7 +74,7 @@ module Xcov
74
74
 
75
75
  # Class methods
76
76
 
77
- def self.map (dictionary)
77
+ def self.map(dictionary)
78
78
  name = dictionary["name"]
79
79
  location = dictionary["location"]
80
80
  coverage = dictionary["coverage"]
@@ -83,12 +83,12 @@ module Xcov
83
83
  Source.new(name, location, coverage, functions, lines)
84
84
  end
85
85
 
86
- def self.map_lines (dictionaries)
86
+ def self.map_lines(dictionaries)
87
87
  return nil if dictionaries.nil?
88
88
  dictionaries.map { |line| Line.map(line) }
89
89
  end
90
90
 
91
- def self.type (name)
91
+ def self.type(name)
92
92
  types_map = {
93
93
  ".swift" => "swift",
94
94
  ".m" => "objc",
@@ -9,7 +9,7 @@ module Xcov
9
9
  attr_accessor :files
10
10
  attr_accessor :file_templates
11
11
 
12
- def initialize (name, executable, covered, files)
12
+ def initialize(name, executable, covered, files)
13
13
  @name = CGI::escapeHTML(name)
14
14
  @executable_lines = executable
15
15
  @covered_lines = covered
@@ -56,7 +56,7 @@ module Xcov
56
56
 
57
57
  # Class methods
58
58
 
59
- def self.map (dictionary)
59
+ def self.map(dictionary)
60
60
  name = dictionary["name"]
61
61
  files = dictionary["files"].map { |file| Source.map(file)}
62
62
  files = files.sort &by_coverage_with_ignored_at_the_end
@@ -86,7 +86,7 @@ module Xcov
86
86
  files.select { |file| !file.ignored }
87
87
  end
88
88
 
89
- def self.calculate_number_of_covered_lines (files)
89
+ def self.calculate_number_of_covered_lines(files)
90
90
  return 0 if files.nil? || files.empty?
91
91
 
92
92
  files.reduce(0) do |partial_result, file|
@@ -94,7 +94,7 @@ module Xcov
94
94
  end
95
95
  end
96
96
 
97
- def self.calculate_number_of_executable_lines (files)
97
+ def self.calculate_number_of_executable_lines(files)
98
98
  return 0 if files.nil? || files.empty?
99
99
 
100
100
  files.reduce(0) do |partial_result, file|
data/lib/xcov/options.rb CHANGED
@@ -7,131 +7,198 @@ module Xcov
7
7
  def self.available_options
8
8
  containing = FastlaneCore::Helper.fastlane_enabled? ? './fastlane' : '.'
9
9
 
10
- [
11
- FastlaneCore::ConfigItem.new(key: :workspace,
12
- short_option: "-w",
13
- env_name: "XCOV_WORKSPACE",
14
- optional: true,
15
- description: "Path the workspace file",
16
- verify_block: proc do |value|
17
- v = File.expand_path(value.to_s)
18
- raise "Workspace file not found at path '#{v}'".red unless File.exist?(v)
19
- raise "Workspace file invalid".red unless File.directory?(v)
20
- raise "Workspace file is not a workspace, must end with .xcworkspace".red unless v.include?(".xcworkspace")
21
- end),
22
- FastlaneCore::ConfigItem.new(key: :project,
23
- short_option: "-p",
24
- optional: true,
25
- env_name: "XCOV_PROJECT",
26
- description: "Path the project file",
27
- verify_block: proc do |value|
28
- v = File.expand_path(value.to_s)
29
- raise "Project file not found at path '#{v}'".red unless File.exist?(v)
30
- raise "Project file invalid".red unless File.directory?(v)
31
- raise "Project file is not a project file, must end with .xcodeproj".red unless v.include?(".xcodeproj")
32
- end),
33
- FastlaneCore::ConfigItem.new(key: :scheme,
34
- short_option: "-s",
35
- optional: true,
36
- env_name: "XCOV_SCHEME",
37
- description: "The project's scheme. Make sure it's marked as `Shared`"),
38
- FastlaneCore::ConfigItem.new(key: :configuration,
39
- short_option: "-q",
40
- env_name: "XCOV_CONFIGURATION",
41
- description: "The configuration used when building the app. Defaults to 'Release'",
42
- optional: true),
43
- FastlaneCore::ConfigItem.new(key: :source_directory,
44
- short_option: "-r",
45
- optional: true,
46
- env_name: "XCOV_SOURCE_DIRECTORY",
47
- description: "The path to project's root directory",
48
- verify_block: proc do |value|
49
- v = File.expand_path(value.to_s)
50
- raise "Specified source directory does not exist".red unless File.exist?(v)
51
- raise "Invalid source directory path, it must point to a directory".red unless File.directory?(v)
52
- end
53
- ),
54
- FastlaneCore::ConfigItem.new(key: :derived_data_path,
55
- short_option: "-j",
56
- env_name: "XCOV_DERIVED_DATA_PATH",
57
- description: "The directory where build products and other derived data will go",
58
- optional: true,
59
- verify_block: proc do |value|
60
- v = File.expand_path(value.to_s)
61
- raise "Specified derived data directory does not exist".red unless File.exist?(v)
62
- raise "Invalid derived data path, it must point to a directory".red unless File.directory?(v)
63
- end),
64
- FastlaneCore::ConfigItem.new(key: :output_directory,
65
- short_option: "-o",
66
- env_name: "XCOV_OUTPUT_DIRECTORY",
67
- description: "The directory in which all reports will be stored",
68
- default_value: File.join(containing, "xcov_report")),
69
- FastlaneCore::ConfigItem.new(key: :html_report,
70
- env_name: "XCOV_HTML_REPORT",
71
- description: "Produce an HTML report",
72
- optional: true,
73
- is_string: false,
74
- default_value: true),
75
- FastlaneCore::ConfigItem.new(key: :markdown_report,
76
- env_name: "XCOV_MARKDOWN_REPORT",
77
- description: "Produce a Markdown report",
78
- optional: true,
79
- is_string: false,
80
- default_value: false),
81
- FastlaneCore::ConfigItem.new(key: :json_report,
82
- env_name: "XCOV_JSON_REPORT",
83
- description: "Produce a JSON report",
84
- optional: true,
85
- is_string: false,
86
- default_value: false),
87
- FastlaneCore::ConfigItem.new(key: :minimum_coverage_percentage,
88
- short_option: "-m",
89
- env_name: "XCOV_MINIMUM_COVERAGE_PERCENTAGE",
90
- description: "Raise exception if overall coverage percentage is under this value (ie. 75)",
91
- type: Float,
92
- default_value: 0),
93
- FastlaneCore::ConfigItem.new(key: :ignore_file_path,
94
- short_option: "-x",
95
- env_name: "XCOV_IGNORE_FILE_PATH",
96
- description: "Relative or absolute path to the file containing the list of ignored files",
97
- default_value: File.join(containing, ".xcovignore")),
98
- FastlaneCore::ConfigItem.new(key: :include_test_targets,
99
- env_name: "XCOV_INCLUDE_TEST_TARGETS",
100
- description: "Enables coverage reports for .xctest targets",
101
- is_string: false,
102
- default_value: false),
103
- FastlaneCore::ConfigItem.new(key: :slack_url,
104
- short_option: "-i",
105
- env_name: "SLACK_URL",
106
- description: "Create an Incoming WebHook for your Slack group to post results there",
107
- optional: true,
108
- verify_block: proc do |value|
109
- raise "Invalid URL, must start with https://" unless value.start_with? "https://"
110
- end),
111
- FastlaneCore::ConfigItem.new(key: :slack_channel,
112
- short_option: "-e",
113
- env_name: "XCOV_SLACK_CHANNEL",
114
- description: "#channel or @username",
115
- optional: true),
116
- FastlaneCore::ConfigItem.new(key: :skip_slack,
117
- description: "Don't publish to slack, even when an URL is given",
118
- is_string: false,
119
- default_value: false),
120
- FastlaneCore::ConfigItem.new(key: :exclude_targets,
121
- optional: true,
122
- conflicting_options: [:include_targets, :only_project_targets],
123
- description: "Comma separated list of targets to exclude from coverage report"),
124
- FastlaneCore::ConfigItem.new(key: :include_targets,
125
- optional: true,
126
- conflicting_options: [:exclude_targets, :only_project_targets],
127
- description: "Comma separated list of targets to include in coverage report. If specified then exlude_targets will be ignored"),
128
- FastlaneCore::ConfigItem.new(key: :only_project_targets,
129
- optional: true,
130
- conflicting_options: [:exclude_targets, :include_targets],
131
- description: "Display the coverage only for main project targets (e.g. skip Pods targets)",
132
- is_string: false,
133
- default_value: false)
10
+ return [
134
11
 
12
+ # Project options
13
+ FastlaneCore::ConfigItem.new(
14
+ key: :workspace,
15
+ short_option: "-w",
16
+ env_name: "XCOV_WORKSPACE",
17
+ optional: true,
18
+ description: "Path the workspace file",
19
+ verify_block: proc do |value|
20
+ v = File.expand_path(value.to_s)
21
+ raise "Workspace file not found at path '#{v}'".red unless File.exist?(v)
22
+ raise "Workspace file invalid".red unless File.directory?(v)
23
+ raise "Workspace file is not a workspace, must end with .xcworkspace".red unless v.include?(".xcworkspace")
24
+ end
25
+ ),
26
+ FastlaneCore::ConfigItem.new(
27
+ key: :project,
28
+ short_option: "-p",
29
+ optional: true,
30
+ env_name: "XCOV_PROJECT",
31
+ description: "Path the project file",
32
+ verify_block: proc do |value|
33
+ v = File.expand_path(value.to_s)
34
+ raise "Project file not found at path '#{v}'".red unless File.exist?(v)
35
+ raise "Project file invalid".red unless File.directory?(v)
36
+ raise "Project file is not a project file, must end with .xcodeproj".red unless v.include?(".xcodeproj")
37
+ end
38
+ ),
39
+ FastlaneCore::ConfigItem.new(
40
+ key: :scheme,
41
+ short_option: "-s",
42
+ optional: true,
43
+ env_name: "XCOV_SCHEME",
44
+ description: "The project's scheme. Make sure it's marked as `Shared`"
45
+ ),
46
+ FastlaneCore::ConfigItem.new(
47
+ key: :configuration,
48
+ short_option: "-q",
49
+ env_name: "XCOV_CONFIGURATION",
50
+ description: "The configuration used when building the app. Defaults to 'Release'",
51
+ optional: true
52
+ ),
53
+ FastlaneCore::ConfigItem.new(
54
+ key: :source_directory,
55
+ short_option: "-r",
56
+ optional: true,
57
+ env_name: "XCOV_SOURCE_DIRECTORY",
58
+ description: "The path to project's root directory",
59
+ verify_block: proc do |value|
60
+ v = File.expand_path(value.to_s)
61
+ raise "Specified source directory does not exist".red unless File.exist?(v)
62
+ raise "Invalid source directory path, it must point to a directory".red unless File.directory?(v)
63
+ end
64
+ ),
65
+ FastlaneCore::ConfigItem.new(
66
+ key: :derived_data_path,
67
+ short_option: "-j",
68
+ env_name: "XCOV_DERIVED_DATA_PATH",
69
+ description: "The directory where build products and other derived data will go",
70
+ optional: true,
71
+ verify_block: proc do |value|
72
+ v = File.expand_path(value.to_s)
73
+ raise "Specified derived data directory does not exist".red unless File.exist?(v)
74
+ raise "Invalid derived data path, it must point to a directory".red unless File.directory?(v)
75
+ end
76
+ ),
77
+ FastlaneCore::ConfigItem.new(
78
+ key: :output_directory,
79
+ short_option: "-o",
80
+ env_name: "XCOV_OUTPUT_DIRECTORY",
81
+ description: "The directory in which all reports will be stored",
82
+ default_value: File.join(containing, "xcov_report")
83
+ ),
84
+
85
+ # Report options
86
+ FastlaneCore::ConfigItem.new(
87
+ key: :html_report,
88
+ env_name: "XCOV_HTML_REPORT",
89
+ description: "Produce an HTML report",
90
+ optional: true,
91
+ is_string: false,
92
+ default_value: true
93
+ ),
94
+ FastlaneCore::ConfigItem.new(
95
+ key: :markdown_report,
96
+ env_name: "XCOV_MARKDOWN_REPORT",
97
+ description: "Produce a Markdown report",
98
+ optional: true,
99
+ is_string: false,
100
+ default_value: false
101
+ ),
102
+ FastlaneCore::ConfigItem.new(
103
+ key: :json_report,
104
+ env_name: "XCOV_JSON_REPORT",
105
+ description: "Produce a JSON report",
106
+ optional: true,
107
+ is_string: false,
108
+ default_value: false
109
+ ),
110
+ FastlaneCore::ConfigItem.new(
111
+ key: :minimum_coverage_percentage,
112
+ short_option: "-m",
113
+ env_name: "XCOV_MINIMUM_COVERAGE_PERCENTAGE",
114
+ description: "Raise exception if overall coverage percentage is under this value (ie. 75)",
115
+ type: Float,
116
+ default_value: 0
117
+ ),
118
+
119
+ # Slack options
120
+ FastlaneCore::ConfigItem.new(
121
+ key: :slack_url,
122
+ short_option: "-i",
123
+ env_name: "SLACK_URL",
124
+ description: "Create an Incoming WebHook for your Slack group to post results there",
125
+ optional: true,
126
+ verify_block: proc do |value|
127
+ raise "Invalid URL, must start with https://" unless value.start_with? "https://"
128
+ end
129
+ ),
130
+ FastlaneCore::ConfigItem.new(
131
+ key: :slack_channel,
132
+ short_option: "-e",
133
+ env_name: "XCOV_SLACK_CHANNEL",
134
+ description: "#channel or @username",
135
+ optional: true
136
+ ),
137
+ FastlaneCore::ConfigItem.new(
138
+ key: :skip_slack,
139
+ description: "Don't publish to slack, even when an URL is given",
140
+ is_string: false,
141
+ default_value: false
142
+ ),
143
+
144
+ # Exclusion options
145
+ FastlaneCore::ConfigItem.new(
146
+ key: :ignore_file_path,
147
+ short_option: "-x",
148
+ env_name: "XCOV_IGNORE_FILE_PATH",
149
+ description: "Relative or absolute path to the file containing the list of ignored files",
150
+ default_value: File.join(containing, ".xcovignore")
151
+ ),
152
+ FastlaneCore::ConfigItem.new(
153
+ key: :include_test_targets,
154
+ env_name: "XCOV_INCLUDE_TEST_TARGETS",
155
+ description: "Enables coverage reports for .xctest targets",
156
+ is_string: false,
157
+ default_value: false
158
+ ),
159
+ FastlaneCore::ConfigItem.new(
160
+ key: :exclude_targets,
161
+ optional: true,
162
+ conflicting_options: [:include_targets, :only_project_targets],
163
+ description: "Comma separated list of targets to exclude from coverage report"
164
+ ),
165
+ FastlaneCore::ConfigItem.new(
166
+ key: :include_targets,
167
+ optional: true,
168
+ conflicting_options: [:exclude_targets, :only_project_targets],
169
+ description: "Comma separated list of targets to include in coverage report. If specified then exlude_targets will be ignored"
170
+ ),
171
+ FastlaneCore::ConfigItem.new(
172
+ key: :only_project_targets,
173
+ optional: true,
174
+ conflicting_options: [:exclude_targets, :include_targets],
175
+ description: "Display the coverage only for main project targets (e.g. skip Pods targets)",
176
+ is_string: false,
177
+ default_value: false
178
+ ),
179
+
180
+ # Coveralls options
181
+ FastlaneCore::ConfigItem.new(
182
+ key: :coveralls_service_name,
183
+ env_name: "COVERALLS_SERVICE_NAME",
184
+ optional: true,
185
+ conflicting_options: [:coveralls_repo_token],
186
+ description: "Name of the CI service compatible with Coveralls. i.e. travis-ci. This option must be defined along with coveralls_service_job_id"
187
+ ),
188
+ FastlaneCore::ConfigItem.new(
189
+ key: :coveralls_service_job_id,
190
+ env_name: "COVERALLS_SERVICE_JOB_ID",
191
+ optional: true,
192
+ conflicting_options: [:coveralls_repo_token],
193
+ description: "Name of the current job running on a CI service compatible with Coveralls. This option must be defined along with coveralls_service_name"
194
+ ),
195
+ FastlaneCore::ConfigItem.new(
196
+ key: :coveralls_repo_token,
197
+ env_name: "COVERALLS_REPO_TOKEN",
198
+ optional: true,
199
+ conflicting_options: [:coveralls_service_name, :coveralls_service_job_id],
200
+ description: "Repository token to be used by integrations not compatible with Coveralls"
201
+ )
135
202
  ]
136
203
  end
137
204
 
@@ -10,14 +10,14 @@ module FastlaneCore
10
10
  return [] if project_path.nil?
11
11
 
12
12
  proj = Xcodeproj::Project.open(project_path)
13
-
13
+
14
14
  proj.targets.map do |target|
15
15
  target.name
16
16
  end
17
17
  end
18
18
 
19
19
  private
20
-
20
+
21
21
  def get_project_path
22
22
  # Given the workspace and scheme, we can compute project path
23
23
  if workspace?
@@ -25,9 +25,11 @@ module Xcov
25
25
  }
26
26
  end
27
27
 
28
- result = notifier.ping "Your *xcov* coverage report",
29
- icon_url: 'https://s3-eu-west-1.amazonaws.com/fastlane.tools/fastlane.png',
30
- attachments: attachments
28
+ result = notifier.ping(
29
+ "Your *xcov* coverage report",
30
+ icon_url: 'https://s3-eu-west-1.amazonaws.com/fastlane.tools/fastlane.png',
31
+ attachments: attachments
32
+ )
31
33
 
32
34
  if result.code.to_i == 200
33
35
  UI.message 'Successfully sent Slack notification'.green
data/lib/xcov/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Xcov
2
2
 
3
- VERSION = "1.0.1"
3
+ VERSION = "1.1.0"
4
4
  DESCRIPTION = "xcov is a friendly visualizer for Xcode's code coverage files"
5
5
 
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xcov
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Vidal
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-12 00:00:00.000000000 Z
11
+ date: 2017-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fastlane
@@ -72,6 +72,20 @@ dependencies:
72
72
  - - ">="
73
73
  - !ruby/object:Gem::Version
74
74
  version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: multipart-post
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
75
89
  - !ruby/object:Gem::Dependency
76
90
  name: bundler
77
91
  requirement: !ruby/object:Gem::Requirement
@@ -212,6 +226,7 @@ files:
212
226
  - lib/xcov-core/version.rb
213
227
  - lib/xcov.rb
214
228
  - lib/xcov/commands_generator.rb
229
+ - lib/xcov/coveralls_handler.rb
215
230
  - lib/xcov/error_handler.rb
216
231
  - lib/xcov/ignore_handler.rb
217
232
  - lib/xcov/manager.rb
@@ -224,7 +239,6 @@ files:
224
239
  - lib/xcov/model/target.rb
225
240
  - lib/xcov/options.rb
226
241
  - lib/xcov/project_extensions.rb
227
- - lib/xcov/runner.rb
228
242
  - lib/xcov/slack_poster.rb
229
243
  - lib/xcov/version.rb
230
244
  - views/file.erb
data/lib/xcov/runner.rb DELETED
@@ -1,123 +0,0 @@
1
- require 'pty'
2
- require 'open3'
3
- require 'fileutils'
4
- require 'terminal-table'
5
- require 'xcov-core'
6
- require 'pathname'
7
- require 'json'
8
-
9
- module Xcov
10
- class Runner
11
-
12
- def run
13
- report_json = parse_xccoverage
14
- report = generate_xcov_report(report_json)
15
- validate_report(report)
16
- end
17
-
18
- def parse_xccoverage
19
- # Find .xccoverage file
20
- test_logs_path = derived_data_path + "Logs/Test/"
21
- xccoverage_files = Dir["#{test_logs_path}*.xccoverage"].sort_by { |filename| File.mtime(filename) }.reverse
22
-
23
- unless test_logs_path.directory? && !xccoverage_files.empty?
24
- ErrorHandler.handle_error("CoverageNotFound")
25
- end
26
-
27
- Xcov::Core::Parser.parse(xccoverage_files.first)
28
- end
29
-
30
- def generate_xcov_report report_json
31
- # Create output path
32
- output_path = Xcov.config[:output_directory]
33
- FileUtils.mkdir_p(output_path)
34
-
35
- # Convert report to xCov model objects
36
- report = Report.map(report_json)
37
-
38
- if Xcov.config[:html_report] then
39
- resources_path = File.join(output_path, "resources")
40
- FileUtils.mkdir_p(resources_path)
41
-
42
- # Copy images to output resources folder
43
- Dir[File.join(File.dirname(__FILE__), "../../assets/images/*")].each do |path|
44
- FileUtils.cp_r(path, resources_path)
45
- end
46
-
47
- # Copy stylesheets to output resources folder
48
- Dir[File.join(File.dirname(__FILE__), "../../assets/stylesheets/*")].each do |path|
49
- FileUtils.cp_r(path, resources_path)
50
- end
51
-
52
- # Copy javascripts to output resources folder
53
- Dir[File.join(File.dirname(__FILE__), "../../assets/javascripts/*")].each do |path|
54
- FileUtils.cp_r(path, resources_path)
55
- end
56
-
57
- # Create HTML report
58
- File.open(File.join(output_path, "index.html"), "wb") do |file|
59
- file.puts report.html_value
60
- end
61
- end
62
-
63
- # Create Markdown report
64
- if Xcov.config[:markdown_report] then
65
- File.open(File.join(output_path, "report.md"), "wb") do |file|
66
- file.puts report.markdown_value
67
- end
68
- end
69
-
70
- # Create JSON report
71
- if Xcov.config[:json_report] then
72
- File.open(File.join(output_path, "report.json"), "wb") do |file|
73
- file.puts report.json_value.to_json
74
- end
75
- end
76
-
77
- # Post result
78
- SlackPoster.new.run(report)
79
-
80
- # Print output
81
- table_rows = []
82
- report.targets.each do |target|
83
- table_rows << [target.name, target.displayable_coverage]
84
- end
85
- puts Terminal::Table.new({
86
- title: "xcov Coverage Report".green,
87
- rows: table_rows
88
- })
89
- puts ""
90
-
91
- # Raise exception in case of failure
92
- raise "Unable to create coverage report" if report.nil?
93
-
94
- report
95
- end
96
-
97
- def validate_report report
98
- exit_status = 0
99
-
100
- # Raise exception if overall coverage
101
- minimumPercentage = Xcov.config[:minimum_coverage_percentage] / 100
102
- if minimumPercentage > report.coverage
103
- exit_status = 1
104
-
105
- UI.user_error!("Actual Code Coverage (#{"%.2f%" % (report.coverage*100)}) below threshold of #{"%.2f%" % (minimumPercentage*100)}")
106
- end
107
-
108
- exit_status
109
- end
110
-
111
- # Auxiliar methods
112
-
113
- def derived_data_path
114
- # If DerivedData path was supplied, return
115
- return Pathname.new(Xcov.config[:derived_data_path]) unless Xcov.config[:derived_data_path].nil?
116
-
117
- # Otherwise check project file
118
- product_builds_path = Pathname.new(Xcov.project.default_build_settings(key: "SYMROOT"))
119
- return product_builds_path.parent.parent
120
- end
121
-
122
- end
123
- end