中野智文

中野智文(VOYAGE GROUP)のコンピュータなどのメモ

Guard::RSpec が無反応だった時やったこと

2015-05-15 10:37:32 | ruby

背景

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になっているから、ということ。

最新の画像もっと見る

1 コメント

コメント日が  古い順  |   新しい順
素晴らしい (satosi)
2016-05-12 10:48:28
Redmineのプラグインを作成しています。
こちらの記事がどんぴしゃで役に立ちました。
ありがとうございました。
返信する

コメントを投稿

ブログ作成者から承認されるまでコメントは反映されません。