Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

Circle CI で bundle outdated を実行し、結果を JUnit xml にすることでレポートを生成する

alt

Circle CI で bundle outdated を実行し、結果を JUnit フォーマットの xml にすることでレポートを生成します。

budle outdated 関連はいろんな人が自動化していて、特に新規性はないのですが
JUnit フォーマットの xml を自前で作成してみるのを試したくてこのお題にしました。

JUnit XML 形式はCircle CI など多くのCIサービスのテストや静的解析の実行時に
レポート表示がサポートされていることが多いです。 (全ては把握してないので曖昧な表現)

eslint junit のサンプル

参考事例として ESLint が出力するJUnitフォーマットの出力を確認してみます。

ESLint は標準でJUnitフォーマットをサポートしています。
例えば以下のようなコマンドで JUnit フォーマットの結果を出力できます。

$ eslint --format=junit ./src

実際に出力されるXMLを確認してみます。

  • testsuite はファイル単位
  • testcase は個別の警告単位。エラーメッセージに詳細と行数を記載
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
   <testsuite package="org.eslint" time="0" tests="2" errors="2" name="/path/to/eslint_ci_test/src/hoge.js">
      <testcase time="0" name="org.eslint.semi">
         <failure message="Missing semicolon."><![CDATA[line 10, col 37, Error - Missing semicolon. (semi)]]></failure>
      </testcase>
      <testcase time="0" name="org.eslint.indent">
         <failure message="Expected indentation of 8 spaces but found 10."><![CDATA[line 11, col 11, Error - Expected indentation of 8 spaces but found 10. (indent)]]></failure>
      </testcase>
   </testsuite>
   <testsuite package="org.eslint" time="0" tests="2" errors="2" name="/path/to/eslint_ci_test/src/index.js">
      <testcase time="0" name="org.eslint.semi">
         <failure message="Missing semicolon."><![CDATA[line 10, col 37, Error - Missing semicolon. (semi)]]></failure>
      </testcase>
      <testcase time="0" name="org.eslint.indent">
         <failure message="Expected indentation of 8 spaces but found 10."><![CDATA[line 11, col 11, Error - Expected indentation of 8 spaces but found 10. (indent)]]></failure>
      </testcase>
   </testsuite>
</testsuites>

サンプル

Gemfile

source "https://rubygems.org"

gem 'eto', '~> 1.0.0'
gem 'kosi', '~> 0.0.1'
gem 'ruboty-generator', '~> 1.0.0'

script/bundle_outdated.rb

require 'open3'
require 'erb'

class BundleOutdated
  JUNIT_XML_TEMPLATE = <<-EOS.freeze
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
  <testsuite name="outdated">
<%=testcases%>
  </testsuite>
</testsuites>
  EOS

  def self.outdated_gems
    Open3.capture2('bundle outdated --porcelain').first[1..-1]
  end

  def self.generate(testcases)
    ERB.new(JUNIT_XML_TEMPLATE).result(binding)
  end

  def self.generate_testsuite(gem)
    gem.match(/(.+)\s\((.+)\)/)
    name = $1
    description = $2
    <<-EOS.chomp
    <testcase time="0" name="#{name}">
      <failure message="#{description}"><![CDATA[https://rubygems.org/gems/#{name}]]></failure>
    </testcase>
    EOS
  end
end

outdated_gems = BundleOutdated.outdated_gems
testcases = outdated_gems.each_line.map(&:chomp).each_with_object([]) do |gem, memo|
  memo << BundleOutdated.generate_testsuite(gem)
end

exit(0) if testcases.size.zero?
puts BundleOutdated.generate(testcases.join("\n"))
exit(1)

circle.yml

machine:
  timezone:
    Asia/Tokyo
  ruby:
    version: 2.3.1
test:
  pre:
    - mkdir $CIRCLE_TEST_REPORTS/bundle_outdated
    - ruby $HOME/$CIRCLE_PROJECT_REPONAME/script/bundle_outdated.rb > $CIRCLE_TEST_REPORTS/bundle_outdated/bundle_outdated.xml

実行

Circle CIにpushして動作確認しました

f:id:tbpg:20161013222257p:plain

関連資料