Skip to content

Instantly share code, notes, and snippets.

@nobuoka
Last active March 24, 2021 08:27
Show Gist options
  • Save nobuoka/04505758434a233fd5cc9a901b3e9839 to your computer and use it in GitHub Desktop.
Save nobuoka/04505758434a233fd5cc9a901b3e9839 to your computer and use it in GitHub Desktop.
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