TravisCIにせよCircle CIにせよ通常その結果を得るには「取れるところにおいて取ってくればいい、終了!」なんだけど、人類には早すぎる並列実行テクノロジーをやむを得ず使わざるを得ない場合には人智を越えるので大変なわけです。
その具体例として(?)今回はCircle CIでparallel実行したsimplecovの結果収集を取り上げます。
対象のソースコードの取得
後述しますが、まず対象のソースコードが必要です。GitHubからの場合、https://github.com/<username>/<reponame>/archive/<commit>.tar.gz
を取ってきて、tar -C /tmp -xf repo.tar.gz
するのが楽でしょう。Private Repositoryの場合はOauth tokenを取得してBasic Authに<username>:<token>
でいけます。
.resultset.jsonの収集
GET: /project/:username/:project/:build_num/artifacts のJSONから適当に取ってきてください。
.resultset.jsonのマージ
落とし穴がたくさんありますが、結論は以下の通りです。
require 'open-uri'
require 'json'
require 'simplecov'
require 'fileutils'
resultset_uris = [] # .resultset.jsonのURIの入った配列
CIRCLECI_APIKEY # Circle CIのapikey
reponame # APIとかから取ってきましょう
vcs_revision # APIとかから取ってきましょう
dirpath = File.join(Dir.tmpdir, "#{reponame}-#{vcs_revision}")
merged_results = resultset_uris.inject({}).do |results, uri|
part = URI("#{url}?circle-token=#{CIRCLECI_APIKEY}").read
part.gsub!(/(?<=")\/[^"]*\/#{reponame}(?=\/)/, dirpath) # ソースファイルのパス書き換え
json = JSON.parse(part)
result = SimpleCov::Result.new(json['RSpec']['coverage'])
result.original_result.merge_resultset(results)
end
SimpleCov.root(dirpath) # Simplecov.filtered()のroot_filter回避
res = SimpleCov::Result.new(merged_results) # マージ完了!
FileUtils.remove_entry_secure(dirpath, true) # ソースコード削除
# 例えばカバレッジ率が欲しい場合
puts "%.2f" % res.covered_percent
ポイント
- resultset から参照しているファイルは実際にそこに存在している必要がある。(だから、最初にソースコードを取ってきたわけです)
- require 'simplecov'した時点のカレントディレクトリ下にソースコードがある。外にあると
Simplecov.filtered()
のroot_filterでそのファイルは除外されてしまう。回避するにはSimpleCov.root(repo_path)を呼ぶ。
以上を行わなかった場合、covered_percentが常に100になり、SimpleCov::Result#filesは[]になります。
参考資料
HOW WE GET COVERAGE ON PARALLELIZED TEST BUILDS: CoverallsはCircle CIのVMにssh接続してやっているらしい。