AutoGrowling: making autotest and spectest work with Growl

I like test and feature driven development. It keeps me on track when writing ruby code. Since I work on few different projects and some of these projects use TestUnit while others use Rspec I needed the .autospec hook to handle both formats.

So here is another tutorial on how to get these things working together:

  1. Download Growl from: http://growl.info/
  2. Install the downloaded dmg
  3. Install growlnotify from Extras/growlnotify folder in the dmg
  4. Copy the folder to your desktop
    Open the terminal and run following commands.

    $ cd Desktop/growlnotify
    $ sudo sh install.sh
  5. Create
    ~/.autotest_images/

    folder.

  6. Save following images into the folder.

Install following gems: ZenTest, redgreen:

$ [sudo] gem install ZenTest
$ [sudo] gem install redgreen

Download the script below and rename the file to .autotest and place it in your home folder user folder.

##!/usr/bin/ruby
require 'autotest/redgreen'
require 'autotest/timestamp'
 
class AutoTestNotifier
  IMAGEDIR = File.expand_path("~/.autotest_images")
  FAIL_IMG = IMAGEDIR + "/rails_fail.png"
  PASS_IMG = IMAGEDIR + "/rails_pass.png"
  PEND_IMG = IMAGEDIR + "/rails_pending.png"
 
  def self.growl(data)
    AutoTestNotifier.new(data).notify
  end
 
  def initialize(data)
    @priority = nil
    @result = {}
    if data.last =~ /examples/i
      parse_spec_message(data)
    else
      parse_unit_message(data)
    end
  end
 
  def parse_spec_message(data)
    @result[:type] = :spec
    output = data.last.strip.gsub(/\e\[\d+m/, '').split(',')
    output.map do |i| 
      val, lbl = i.strip.split(" ")
      @result[lbl.to_sym] = val.to_i
    end
  end
 
  def parse_unit_message(data)
    @result[:type] = :unit
    output = data.last.chop.split(',')
    output.map do |i| 
      val, lbl = i.strip.split(" ")
      @result[lbl.to_sym] = val.to_i
    end
  end
 
  def img
    return FAIL_IMG if @result[:errors].to_i > 0 || @result[:failures].to_i > 0 
    return PEND_IMG if @result[:pending].to_i > 0 
    return PASS_IMG
  end
 
  def title
    pre = @result[:type] == :spec ? "Spec Tests" : "Unit Tests"
    return "#{pre} Failed:" if @result[:errors].to_i > 0 || @result[:failures].to_i > 0 
    return "#{pre} Pending:" if @result[:pending].to_i > 0 
    return "#{pre} Passed:"
  end
 
  def to_s
    msg = ""
    msg << "Examples: #{@result[:examples]}     " if @result[:examples]
    msg << "Tests: #{@result[:tests]}     " if @result[:tests]
    msg << "Assertions: #{@result[:assertions]}     " if @result[:assertions]
    msg << "Pending: #{@result[:pending]}      " if @result[:pending]
    msg << "Failures: #{@result[:failures]}     "
    msg << "Errors: #{@result[:errors]}" if @result[:errors]
    msg
  end
 
  def priority
    return @priority || 0
  end
 
  def stick
    return ""
  end
 
  def notify
    cmd = "growlnotify -n autotest --image #{img} -p #{priority} -m '#{to_s}' #{title} #{stick}" 
    `#{cmd}`
  end
 
end
 
module Autotest::Growl
  Autotest.add_hook :ran_command do |at|
    AutoTestNotifier.growl(at.results)
  end
end

This should take care for both the UnitTest and RSpec messages.