gemのバグを踏んだときにどうやって立ち向かうのか

最近、 colszowka/simplecov のバグを踏みました。

こうやって 不運にもバグを踏んでしまった 運よくOSSに貢献できるチャンスを得てしまったときの原因調査の方法などを紹介します。

今回のバグ

simplecov はカバレッジ率を計測するgemで、以前から便利に使ってました。
しかし、今回 simplecov v0.11.1 と grosser/parallel_tests を一緒に使ったところ、突然カバレッジ率が下がってしまう問題が起きました。

原因の調べ方

1. breakpoint を仕込む

GitHubソースコードを読んで、処理の流れを理解する。

なんとなく原因かもしれない処理が見つかったら、その近辺のメソッドを上書きして調べる。

module SimpleCovDebug
  def start(profile = nil, &block)
    require 'byebug'
    byebug

    super
  end
end
# https://github.com/colszowka/simplecov/blob/v0.11.1/lib/simplecov.rb
SimpleCov.singleton_class.prepend(SimpleCovDebug)

module ResultDebug
  def initialize(original_result)
    require 'byebug'
    byebug

    super
  end
end
# https://github.com/colszowka/simplecov/blob/v0.11.1/lib/simplecov/result.rb
SimpleCov::Result.prepend(ResultDebug)

適当なmoduleを作って、 prepend で gem のクラスを上書きします。これにより、実行時にbyebugで止まるようになります。*1

2. 原因のコミットを探す

過去のバージョンを試し、バグの原因となったコミットを見つけ出します。

gem 'rubocop', '0.10.0', require: false

0.11.00.10.0 と試し、 0.10.0 では問題が起きていないことが分かりました。次は原因のコミットを調べます。

simplecov のリポジトリを適当な場所にcloneします。

$ git clone https://github.com/colszowka/simplecov.git /tmp/simplecov

次に、gemのインストール先をローカルに変えます。

gem 'rubocop', require: false, path: '/tmp/simplecov'

あとは GitHubv0.10.0...v0.11.0 の compare を見ながら、二分探索で探していきます。

f:id:sinsoku:20160107220933p:plain

コンソールを2つ開き、片方でテスト実行、もう1つでチェックアウトする感じ。*2

# コンソール 1
$ cd /tmp/simplecov
$ git checkout $sha1
# コンソール 2
$ bin/rake parallel:spec

バグの再現するコミットを見つけたら、そのコミットに紐づくPull Requestを見ます。

f:id:sinsoku:20160107221845p:plain

Pull Request を読むと、 require されていないファイルのカバレッジ全行を 0(uncover) で追加する という内容のようです。
これは並列で動くと死にそうですね!

バグを直す

原因が分かったら、ローカルのリポジトリでブランチを作り、直します。

$ cd /tmp/simplecov
$ git checkout -b fix_a_bug
# 直す
$ git commit -m "Fix coverage rate of the parallel_tests"

実際に動かしてみて、直ったことを確認します。

まとめ

あまりgemの直し方を書いてる記事を見かけなかったので、書いてみました。
なにか参考になれば幸いです。

Pull Request

今回の修正は Pull Request を投げているのですが、別の方の修正と似たようなことをやっていてマージされるか微妙です。

Fix coverage rate of the parallel_tests by sinsoku · Pull Request #441 · colszowka/simplecov · GitHub

ruby の coverage.c を確認したり、最小限のパッチになるようにしたつもりなんだけどなぁ…。 もっと英語力を身につける必要があるのかもしれない。

*1:今回は parallel_tests だったので puts などを使う機会が多いでしたが…

*2:今回はコミット少なかったけど、多いときは自動化した方がいい