|
# Copyright 2019 Nobuoka Yuya |
|
# Licensed under the MIT License ( https://opensource.org/licenses/mit-license.php ) |
|
# Original : https://gist.github.com/nobuoka/04505758434a233fd5cc9a901b3e9839 |
|
|
|
require 'open3' |
|
|
|
module Fastlane |
|
module Actions |
|
module SharedValues |
|
GENERIC_TEST_COVERAGE_OUTPUT_FILE = :GENERIC_TEST_COVERAGE_OUTPUT_FILE |
|
end |
|
|
|
class GenericTestCoverageAction < Action |
|
def self.run(params) |
|
result_bundle_pathname = Pathname(params[:result_bundle_path]) |
|
output_file_pathname = Pathname(params[:output_file]) |
|
|
|
FileUtils.mkdir_p output_file_pathname.dirname |
|
File.open(output_file_pathname.to_path, "w") do |output| |
|
generate_generic_coverage_from_result_bundle result_bundle_pathname, output |
|
end |
|
|
|
Actions.lane_context[SharedValues::GENERIC_TEST_COVERAGE_OUTPUT_FILE] = output_file_pathname.realpath.to_path |
|
end |
|
|
|
##################################################### |
|
# @!group Documentation |
|
##################################################### |
|
|
|
def self.description |
|
"Generating generic coverage report for SonarQube" |
|
end |
|
|
|
def self.details |
|
"You can use this action to generate a generic coverage report for SonarQube " + |
|
"from result bundle (using xccov command)." |
|
end |
|
|
|
def self.available_options |
|
[ |
|
FastlaneCore::ConfigItem.new(key: :result_bundle_path, |
|
env_name: "FL_GENERIC_TEST_COVERAGE_RESULT_BUNDLE_PATH", |
|
description: "Result bundle path", |
|
is_string: true), |
|
FastlaneCore::ConfigItem.new(key: :output_file, |
|
env_name: "FL_GENERIC_TEST_COVERAGE_OUTPUT_FILE", |
|
description: "File path of output coverage report", |
|
is_string: true) |
|
] |
|
end |
|
|
|
def self.output |
|
[ |
|
['GENERIC_TEST_COVERAGE_OUTPUT_FILE', 'File path of output coverage report'] |
|
] |
|
end |
|
|
|
def self.return_value |
|
# No return values |
|
end |
|
|
|
def self.authors |
|
["nobuoka"] |
|
end |
|
|
|
def self.is_supported?(platform) |
|
platform == :ios |
|
end |
|
|
|
private_class_method |
|
|
|
def self.generate_generic_coverage_from_result_bundle(result_bundle_pathname, output) |
|
output << "<coverage version=\"1\">\n" |
|
xccov_view_file_list(result_bundle_pathname).each do |target_file_pathname| |
|
target_file_relpath = target_file_pathname.relative_path_from(Pathname.getwd).to_path |
|
coverage_lines = xcov_view_file_coverage result_bundle_pathname, target_file_pathname |
|
|
|
output << " <file path=\"#{target_file_relpath}\">\n" |
|
coverage_lines.each do |c| |
|
output << " <lineToCover lineNumber=\"#{c[:line_number]}\" covered=\"#{c[:covered]}\"/>\n" |
|
end |
|
output << " </file>\n" |
|
end |
|
output << "</coverage>\n" |
|
end |
|
|
|
def self.xccov_view_file_list(result_bundle_pathname) |
|
file_list = execute_command("xcrun xccov view --archive --file-list \"#{result_bundle_pathname.to_path}\"") |
|
|
|
file_list.lines.map { |file_name| |
|
Pathname(file_name.chomp) |
|
}.select { |file_pathname| |
|
file_pathname.extname != ".h" |
|
} |
|
end |
|
|
|
def self.xcov_view_file_coverage(result_bundle_pathname, file_pathname) |
|
result = execute_command("xcrun xccov view --archive --file \"#{file_pathname}\" \"#{result_bundle_pathname}\"") |
|
|
|
coverage = [] |
|
line_pattern = /\A\s*([1-9][0-9]*):\s*(\*|\d+)(?:\s*\[([^\]]*)\])?\s*(?:\n|$)/ |
|
remaining_str = result |
|
while m = (line_pattern.match remaining_str) do |
|
remaining_str = m.post_match |
|
line_number, count_or_not_target, optional_info = m.captures |
|
|
|
if count_or_not_target != '*' then |
|
count = count_or_not_target.to_i |
|
coverage << { line_number: line_number, covered: (count > 0) } |
|
end |
|
end |
|
|
|
raise "Unexpected format (remaining string : #{remaining_str})" if remaining_str !~ /\A\s*\z/ |
|
|
|
coverage |
|
end |
|
|
|
# Implementation of this method is inspired by sh_helper.rb |
|
# See : https://github.com/fastlane/fastlane/blob/f32b007ff45e648b37b6c9c2037ac481f36b7780/fastlane/lib/fastlane/helper/sh_helper.rb |
|
def self.execute_command(cmd) |
|
UI.command cmd |
|
output, error, status = Open3.capture3(cmd) |
|
UI.command_output(error) unless error == '' |
|
if not status.success? then |
|
UI.shell_error!("Shell command exited with exit status #{status.exitstatus} instead of 0.") |
|
end |
|
output |
|
end |
|
end |
|
end |
|
end |