Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
generic_test_coverage.rb (fastlane action)

generic_test_coverage.rb

概要

  • fastlane のアクション
  • iOS プロジェクトのビルド結果である result bundle (.xcresult 拡張子) に含まれるカバレッジ情報から SonarQube の Generic Coverage を生成する
  • 内部では xccov コマンドが使用される

利用方法

ファイルの配置

プロジェクトの fastlane/actions ディレクトリ配下に置く。

Fastfile から利用

下記のような形で利用する。

scan(
  workspace: "Foo.xcworkspace",
  scheme: "Bar",
  configuration: "Debug",
  xcodebuild_command: "env NSUnbufferedIO=YES build-wrapper-macosx-x86 --out-dir out xcodebuild",
  xcargs: "-resultBundlePath build/fastlane/Bar.xcresult -resultBundleVersion 3"
)

generic_test_coverage(
  result_bundle_path: 'build/fastlane/Bar.xcresult',
  output_file: 'build/fastlane/coverage.xml'
)

ライセンス

MIT License

代替品

  • xccov-to-sonarqube-generic.sh : 本アクションと同様のシェルスクリプト
    • SonarQube の開発元である SonarSource 製なので安心
    • ただ、対象ファイルのパスが絶対パスになってしまい、著者の環境ではうまく使えなかった (そのためこのアクションを作成した)
# 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.