背景
guardという、自動テストなどを走らせることが出来る環境がある。 Guardfileというものに、どれを監視して実行させるか書くことが出来る。 これが結構優れていていて、変更のあったファイル名の一部を利用したテストファイルを実行するというようなルールが書けるので 無駄なテストを走らせることがない。 自分は rspec を使っているので、Guard::RSpecを導入しようとしたところ、なぜかguardが無反応。その原因を探ってみた。やったこと
guard を デバッグモードで起動
次のように -d オプションをつけるだけ。guard -dすると、起動しただけでもDEBUGメッセージがズラズラっと出る。
18:48:07 - DEBUG - Notiffany: gntp not available (Please add "gem 'ruby_gntp'" to your Gemfile and run your app with "bundle exec".). 18:48:07 - DEBUG - Notiffany: growl not available (Please add "gem 'growl'" to your Gemfile and run your app with "bundle exec".). 18:48:07 - DEBUG - Notiffany: terminal_notifier not available (Please add "gem 'terminal-notifier-guard'" to your Gemfile and run your app with "bundle exec".). 18:48:07 - DEBUG - Notiffany: libnotify not available (Unsupported platform "darwin13.4.0"). 18:48:07 - DEBUG - Notiffany: notifysend not available (Unsupported platform "darwin13.4.0"). 18:48:07 - DEBUG - Notiffany: notifu not available (Unsupported platform "darwin13.4.0"). 18:48:07 - DEBUG - Command execution: emacsclient --eval '1' 18:48:07 - DEBUG - Notiffany: emacs not available (Emacs client failed). 18:48:07 - DEBUG - Notiffany: tmux not available (:tmux notifier is only available inside a TMux session.). 18:48:07 - DEBUG - Notiffany: file not available (No :path option given). 18:48:07 - DEBUG - Notiffany is using TerminalTitle to send notifications. 18:48:07 - DEBUG - Command execution: hash stty 18:48:08 - DEBUG - Guard starts all plugins 18:48:08 - DEBUG - Hook :start_begin executed for Guard::RSpec 18:48:08 - INFO - Guard::RSpec is running 18:48:08 - DEBUG - Hook :start_end executed for Guard::RSpec 18:48:08 - INFO - Guard is now watching at '設定したパス' 18:48:08 - DEBUG - Start interactor 18:48:08 - DEBUG - Command execution: stty -g 2>/dev/null上記と同じ Notiffany ははっきり言って気にしなくていい。少なくとも私の環境ではこれが原因ではなかった。 次にファイルの更新対象となるファイルの更新(touchやエディタによる更新などで)を行う。うまくいく場合には次のようになる。
10:34:59 - DEBUG - Interactor was stopped or killed 10:34:59 - DEBUG - Command execution: stty 2>/dev/null 10:34:59 - DEBUG - Hook :run_on_modifications_begin executed for Guard::RSpec 10:34:59 - INFO - Running: 対象となるファイル.rb 10:34:59 - DEBUG - Command execution: bundle exec rspec -c -fd -r インストールディレクトリ/gems/guard-rspec-4.5.0/lib/guard/rspec_formatter.rb -f Guard::RSpecFormatter --failure-exit-code 2 対象となるファイル.rb スペックの内容 10:35:03 - DEBUG - Hook :run_on_modifications_end executed for Guard::RSpec 10:35:03 - DEBUG - Start interactor 10:35:03 - DEBUG - Command execution: stty -g 2> /dev/nullここで、
- INFOもDEBUGも何も反応がなかった場合。→ guard には何も見えていない。Guardfile の watch式がおかしい可能性がある。
- 何か出たがおかしい場合。→次を参照。
11:09:49 - DEBUG - Start interactor 11:09:49 - DEBUG - Command execution: stty -g 2>/dev/null 11:09:58 - DEBUG - Interactor was stopped or killed 11:09:58 - DEBUG - Command execution: stty 2>/dev/null 11:09:58 - DEBUG - Hook :run_on_modifications_begin executed for Guard::RSpec 11:09:58 - DEBUG - Hook :run_on_modifications_end executed for Guard::RSpec 11:09:58 - DEBUG - Start interactor 11:09:58 - DEBUG - Command execution: stty -g 2>/dev/nullこの場合、watch式などは問題ない。guardには見えている。だが無視されている。 なぜか、なぜなんだ。しかしDEBUG情報はこれ以上何も答えてくれない。DEBUG情報はこのためのものではないらしい。
watch式のブロックにDEBUG情報を埋め込む
おそらくあなたのGuardfileには次のような感じで書かれているに違いない。guard :rspec, cmd: 'bundle exec rspec -c -fd ' do watch(%r{^test/bin/.+_spec\.rb$}) watch(%r{^bin/(.+)\.rb$}) { |m| "test/bin/#{m[1]}_spec.rb" } watch('test/spec_helper.rb') { "test" } endデバッグ情報に何か表示されたので、guardは見ている。まちがいなく見ている。正規表現が間違っているわけではない。 このwatch式はプログラムなのでDEBUG情報を埋め込めるはずだ。なので、埋め込む。
guard :rspec, cmd: 'bundle exec rspec -c -fd ' do watch(%r{^test/bin/.+_spec\.rb$}) { |m| Compat::UI.debug(m, reset: true) m } watch(%r{^bin/(.+)\.rb$}) { |m| Compat::UI.debug(m, reset: true) "test/bin/#{m[1]}_spec.rb" } watch('test/spec_helper.rb') { "test" } endそして、対象のファイルを更新すると、
11:33:48 - DEBUG - Interactor was stopped or killed 11:33:48 - DEBUG - Command execution: stty 2>/dev/null 11:33:48 - DEBUG - ["対象のファイル", "正規表現の括弧の部分の抽出"] 11:33:48 - DEBUG - Hook :run_on_modifications_begin executed for Guard::RSpec 11:33:48 - DEBUG - Hook :run_on_modifications_end executed for Guard::RSpec 11:33:48 - DEBUG - ["bin/.#対象のファイル", ".#括弧の部分の抽出"] 11:33:48 - DEBUG - Start interactor 11:33:48 - DEBUG - Command execution: stty -g 2>/dev/nullおや、.#ってなんだろう。調べてみると、emacs のロックファイルらしい。原因はこれか! 正規表現を修正
guard :rspec, cmd: 'bundle exec rspec -c -fd ' do watch(%r{^test/bin/[\w\d]+_spec\.rb$}) { |m| Compat::UI.debug(m, reset: true) m } watch(%r{^bin/([\w\d]+)\.rb$}) { |m| Compat::UI.debug(m, reset: true) "test/bin/#{m[1]}_spec.rb" } watch('test/spec_helper.rb') { "test" } endそして更新、実行。
11:44:17 - DEBUG - Interactor was stopped or killed 11:44:17 - DEBUG - Command execution: stty 2>/dev/null 11:44:17 - DEBUG - ["対象のファイル", "正規表現の括弧の部分の抽出"] 11:44:17 - DEBUG - Hook :run_on_modifications_begin executed for Guard::RSpec 11:44:17 - DEBUG - Hook :run_on_modifications_end executed for Guard::RSpec 11:44:17 - DEBUG - Start interactor 11:44:17 - DEBUG - Command execution: stty -g 2>/dev/null例のロックファイルは消えたが、これが原因ではないらしい。
トレーサーを仕込む
もう直接プログラムを追うしかない。Tracerというライブラリを使う。require 'tracer' Tracer.add_filter {|event, file, line, id, binding, klass| klass.to_s =~ /guard/i } guard :rspec, cmd: 'bundle exec rspec -c -fd ' do watch(%r{^test/bin/[\w\d]+_spec\.rb$}) { |m| Compat::UI.debug(m, reset: true) Tracer.on m } watch(%r{^bin/([\w\d]+)\.rb$}) { |m| Compat::UI.debug(m, reset: true) Tracer.on "test/bin/#{m[1]}_spec.rb" } watch('test/spec_helper.rb') { "test" } end当たり前だが、大量にトレースログが出力される。
トレーサーのログは、変数の中身まで見ることは出来ない。 よってtracerの情報を元にGuard::RSpecの怪しい箇所に直接デバッグ表示のコードをぶち込む。 その結果、base_inspector.rb
@spec_paths = @options[:spec_paths]という箇所があり、それがrspecの対象とするべきかのチェックに使われている模様。 確かにguard-rspecのオプションの説明にも書かれている。
Specify a custom array of paths that contain spec filesざっくり直訳すると、「specファイルを含むパスのカスタム配列の設定」。うむむ…。
spec_pathsを設定する
これを追加する。require 'tracer' Tracer.add_filter {|event, file, line, id, binding, klass| klass.to_s =~ /guard/i } guard :rspec, cmd: 'bundle exec rspec -c -fd ', spec_paths: ['test/bin'] do watch(%r{^test/bin/[\w\d]+_spec\.rb$}) { |m| Compat::UI.debug(m, reset: true) m } watch(%r{^bin/([\w\d]+)\.rb$}) { |m| Compat::UI.debug(m, reset: true) "test/bin/#{m[1]}_spec.rb" } watch('test/spec_helper.rb') { "test" } endやたー。うまくいったー。しかし他の人の例にはこのような設定がないのはなぜ??? (※1 理由は追記に。)
まとめ
Guard::RSpecに無視されたら、specファイルがあるパスを、Gaurdfileのguardルールにspec_paths オプションに配列として書く。
追記
- 2015/05/18 他の人の設定例で、spec_pathsを書いていない理由が、なんとなく分かっていたのだが、spec_paths のデフォルトが%w(spec)なので、多くの人は、specのディレクトリが、specになっているから、ということ。
こちらの記事がどんぴしゃで役に立ちました。
ありがとうございました。