autotestのGrowl通知をカスタマイズする

以下の設定ファイルでGrowlを有効にしたautotestは、テスト結果をGrowlメッセージとして教えてくれるのでとても快適なのだが、いくつか気になる点も出てきた。

# autotest設定ファイル: ~/.autotest
require 'autotest/growl'
  • 現状では、failuresやerrorsが発生した時は毎回Growlメッセージが通知されるが、テストが成功を繰り返す場合は何も通知されない。
    • 成功している状態は当然の状況なのだから、この仕様は無駄が無くて良いのだが、テストに不慣れな現状では、毎回テスト結果を通知して欲しい気分だ。
  • 通知されるGrowlメッセージは「Test Failed」か「Test Passed」なので、failuresとerrorsの件数はターミナルのテスト結果を見るまで確認できない。
    • Growlメッセージにも「1 tests, 1 assertions, 0 failures, 0 errors」のような情報を付加しておきたい。(問題箇所を特定する為には結局ターミナルで確認する必要があるのだが...。)

そんな要望は、Setting up autotest to use Growl - Knowledge Baseを参考にすることで、叶えることができた。試行錯誤しながら、以下のようにやってみた。

module Autotest::Growlを自分で定義する

  • 今まではrequire 'autotest/growl'を有効にして、module Autotest::Growlを取り込んで機能拡張していた。
  • さらに~/.autotestファイルに直接module Autotest::Growlを定義することで、独自の機能を追加することができるようだ。
  • require 'autotest/growl'は不要になるので、コメントアウトしておいた。有効にしたままではGrowlメッセージが二重に通知されてしまう。
  • ついでに、前回のgem install redgreenによって、今後はrequire 'autotest/redgreen'も不要と考え、コメントアウトしておいた。
#a -*- ruby -*-

# require 'autotest/autoupdate'
# require 'autotest/camping'
# require 'autotest/cctray'
# require 'autotest/emacs'
# require 'autotest/fixtures'
# require 'autotest/growl'
# require 'autotest/heckle'
# require 'autotest/html_report'
# require 'autotest/kdenotify'
# require 'autotest/menu'
# require 'autotest/migrate'
# require 'autotest/notify'
# require 'autotest/pretty'
# require 'autotest/redgreen'
# require 'autotest/screen'
# require 'autotest/shame'
# require 'autotest/snarl'
# require 'autotest/timestamp'

# Autotest::AutoUpdate.sleep_time = 60
# Autotest::AutoUpdate.update_cmd = 'svn up'
# Autotest::Emacs.client_cmd = 'emacsclient -e'
# Autotest::Heckle.flags << '-t test/**/*.rb'
# Autotest::Heckle.klasses << 'MyClass'
# Autotest::Shame.chat_app = :adium

module Autotest::Growl
  def self.growl(title, msg, img, pri=0, sticky="")
    msg += " at #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
    system "growlnotify -n autotest #{title} -m #{msg.inspect} --image #{img} -p #{pri} #{sticky}"
  end

  Autotest.add_hook :ran_command do |at|
    results = [at.results].flatten.join("\n")
    output = results.slice(/(\d+)\s+tests,\s*(\d+)\s+assertions,\s*(\d+)\s+failures,\s*(\d+)\s+errors/)
    if output
      if $~[3].to_i > 0 || $~[4].to_i > 0
        growl "Tests Failed", "#{output}", "~/.rails_fail.png", 2, "-s"
      else
        growl "Tests Passed", "#{output}", "~/.rails_ok.png"
      end
    else
      growl "Tests Failed", "errors", "~/.rails_fail.png", 2, "-s"
    end
  end
end

イコン画像のダウンロード

  • Mac OSX 10.5 Leopardの初期状態では、wgetはインストールされていなかったので、curlで以下のようにダウンロードしておいた。ホームフォルダ直下の不可視ファイルとして保存される。
cd ~
curl http://blog.internautdesign.com/files/rails_fail.png > .rails_fail.png
curl http://blog.internautdesign.com/files/rails_ok.png > .rails_ok.png

メッセージの確認

以上の設定で、Growlメッセージは以下のようになった。

  • ターミナル上でautotestが実行されると、その結果が毎回Growlメッセージで通知される。
  • アイコンがRailsの赤または緑ベースのアイコンになった。
  • テストが失敗した時のGrowlメッセージは自動で消えない。メッセージの上でクリックすることで消える。
    • システム環境設定 >> Growl >> アプリケーション >> autotest >> 通知 >> スティッキー設定で、「アプリケーションによって決定」を選択している状態で。
  • 日時の表示が「2008:03:12 11:21:58」のような書式になった。


何が行われているのか?

growlnotifyコマンドは自在にGrowlメッセージを作る
$ sleep 10; growlnotify -n test "タイトル" -m "メッセージ" --image ~/.rails_fail.png -p 2 -s
  • ターミナルから上記コマンドを実行すると、10秒後に以下のメッセージがGrowlから通知される。

  • つまり「sleep 10;」の部分を「make;」のように差し替えれば、makeコマンドが完了した時に、Growlメッセージが通知されることになるのだ。便利!(以下は利用例)
$ make; growlnotify "make完了!"
  • オプションの意味は以下のようになっている。
オプション文字 オプション引数 引数の書式 オプションの効果
-n test アプリケーション名 システム環境設定 >> Growl >> アプリケーションに登録される名前になる。(省略時はgrowlnotify)
--mage rails_fail.png 画像へのファイルパス ファイルパスの画像がアイコンとして表示される。
-tまたは無し "タイトル" タイトル文字列 メッセージの1行目に太字で大きく表示される。
-m "メッセージ" メッセージ文字列 メッセージの2行目からタイトルよりも細字で小さく表示される。
-p 2 優先順位を表す数値またはキーワード 5段階の優先順位がある。(-2 Very Low, -1 Moderate, 0 Normal, 1 High, 2 Emergency、省略時は0)
-s 無し スティッキー設定*1が有効になる。
module Autotest::Growlは、growlnotifyコマンドを利用している
  • systemメソッドは引数の文字列をシェルコマンドとして実行する。
module Autotest::Growl
  def self.growl(title, msg, img, pri=0, sticky="")
    msg += " at #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
    system "growlnotify -n autotest #{title} -m #{msg.inspect} --image #{img} -p #{pri} #{sticky}"
  end
  • 以下のブロックは、テストが実行された時に呼び出されるようだ。
  Autotest.add_hook :ran_command do |at|
    results = [at.results].flatten.join("\n")
    output = results.slice(/(\d+)\s+tests,\s*(\d+)\s+assertions,\s*(\d+)\s+failures,\s*(\d+)\s+errors/)
    if output
      if $~[3].to_i > 0 || $~[4].to_i > 0
        growl "Tests Failed", "#{output}", "~/.rails_fail.png", 2, "-s"
      else
        growl "Tests Passed", "#{output}", "~/.rails_ok.png"
      end
    else
      growl "Tests Failed", "errors", "~/.rails_fail.png", 2, "-s"
    end
  end
end
  • [at.results]には、シェルコマンドがテストを実行した時に表示される文字列が行ごとの配列として代入されている。
  • 上記を一つの文字列に繋げて、(join(\n))
  • 正規表現/(\d+)\s+tests,\s*(\d+)\s+assertions,\s*(\d+)\s+failures,\s*(\d+)\s+errors/にマッチする部分だけ取り出している。(results.slice)
  • $~[3]は、正規表現の中の()囲まれた3番目の部分を表現している。つまり、3番目の(\d+)とマッチした部分。ここではfailures直前の数字になるはず。
  • 同様に$~[4]は、errors直前の数字になるはず。
  • よって、failuresやerrorsが0より大きいかどうかで(if $~[3].to_i > 0 || $~[4].to_i > 0)、以下のgrowlnotifyコマンドが実行されることになる。
# 0より大きい場合
$ growlnotify -n autotest "Tests Failed" -m "18 tests, 89 assertions, 1 failures, 0 errors at ..." --image ~/.rails_fail.png -p 2 -s

# 0の場合
$ growlnotify -n autotest "Tests Passed" -m "18 tests, 89 assertions, 0 failures, 0 errors at ..." --image ~/.rails_ok.png -p 0


以上の仕組みを応用すれば、自分好みの条件で、シェルコマンドが返す文字列の特定の部分を、Growlに渡して活用することができる!autotest以外でもいろいろな場面で役立ちそうだ!

*1:メッセージが自動的に消えない。クリックすることで消える。