Created
January 10, 2013 19:33
-
-
Save iox/4505069 to your computer and use it in GitHub Desktop.
autotest-growl hack to show rspec error line numbers in Ubuntu notifications
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rubygems' | |
require 'autotest' | |
require 'rbconfig' | |
require File.join(File.dirname(__FILE__), 'result') | |
## | |
# Autotest::Growl | |
# | |
# == FEATUERS: | |
# * Display autotest results as local or remote Growl notifications. | |
# * Clean the terminal on every test cycle while maintaining scrollback. | |
# | |
# == SYNOPSIS: | |
# ~/.autotest | |
# require 'autotest/growl' | |
module Autotest::Growl | |
GEM_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')) | |
@label = '' | |
@modified_files = [] | |
@ran_tests = false | |
@ran_features = false | |
@@remote_notification = false | |
@@one_notification_per_run = false | |
@@sticky_failure_notifications = false | |
@@custom_options = '' | |
@@clear_terminal = true | |
@@hide_label = false | |
@@show_modified_files = false | |
@@image_dir = File.join(GEM_PATH, 'img', 'ruby') | |
## | |
# Whether to use remote or local notificaton (default). | |
def self.remote_notification=(boolean) | |
@@remote_notification = boolean | |
end | |
## | |
# Whether to limit the number of notifications per run to one or not (default). | |
def self.one_notification_per_run=(boolean) | |
@@one_notification_per_run = boolean | |
end | |
## | |
# Whether to make failure and error notifications sticky. | |
def self.sticky_failure_notifications=(boolean) | |
@@sticky_failure_notifications = boolean | |
end | |
## | |
# Custom options passed to the notification binary | |
def self.custom_options=(options) | |
@@custom_options = options | |
end | |
## | |
# Whether to clear the terminal before running tests (default) or not. | |
def self.clear_terminal=(boolean) | |
@@clear_terminal = boolean | |
end | |
## | |
# Whether to display the label (default) or not. | |
def self.hide_label=(boolean) | |
@@hide_label = boolean | |
end | |
## | |
# Whether to display the modified files or not (default). | |
def self.show_modified_files=(boolean) | |
@@show_modified_files = boolean | |
end | |
## | |
# Directory where notification icons can be found | |
def self.image_dir=(path) | |
if File.directory?(File.join(GEM_PATH, 'img', path)) | |
@@image_dir = File.join(GEM_PATH, 'img', path) | |
else | |
@@image_dir = path | |
end | |
end | |
## | |
# Display a message through Growl. | |
def self.growl(title, message, icon, priority=0, sticky=false) | |
growl = File.join(GEM_PATH, 'growl', 'growlnotify') | |
sender = 'Autotest' | |
image = File.join(@@image_dir, "#{icon}.png") | |
case RbConfig::CONFIG['host_os'] | |
when /mac os|darwin/i | |
options = %(-n "#{sender}" --image "#{image}" -p #{priority} -m "#{message}" "#{title}" #{'-s' if sticky} #{@@custom_options}) | |
options << " -H localhost" if @@remote_notification | |
system %(#{growl} #{options} &) | |
when /linux|bsd/i | |
growl = 'notify-send' | |
options = %("#{title}" "#{message}" -i #{image} -t 5000 #{@@custom_options}) | |
system %(#{growl} #{options}) | |
when /windows|mswin|mingw|cygwin/i | |
growl += '.com' | |
image = `cygpath -w #{image}` if RbConfig::CONFIG['host_os'] =~ /cygwin/i | |
options = %(/a:"#{sender}" /n:"#{sender}-#{icon}" /i:"#{image}" /p:#{priority} /t:"#{title}" /s:#{sticky} /silent:true) | |
options << %( /r:"#{sender}-failed","#{sender}-passed","#{sender}-pending","#{sender}-error") | |
system %(#{growl} #{message.inspect} #{options}) | |
else | |
raise "#{RbConfig::CONFIG['host_os']} is not supported by autotest-growl (feel free to submit a patch)" | |
end | |
end | |
## | |
# Display the modified files. | |
Autotest.add_hook :updated do |autotest, modified| | |
@ran_tests = @ran_features = false | |
if @@show_modified_files | |
if modified != @last_modified | |
growl @label + 'Modifications detected.', modified.collect {|m| m[0]}.join(', '), 'info', 0 | |
@last_modified = modified | |
end | |
end | |
false | |
end | |
## | |
# Set the label and clear the terminal. | |
Autotest.add_hook :run_command do | |
@label = File.basename(Dir.pwd).upcase + ': ' if !@@hide_label | |
print "\n"*2 + '-'*80 + "\n"*2 | |
print "\e[2J\e[f" if @@clear_terminal | |
false | |
end | |
## | |
# Parse the RSpec and Test::Unit results and send them to Growl. | |
Autotest.add_hook :ran_command do |autotest| | |
unless @@one_notification_per_run && @ran_tests | |
result = Autotest::Result.new(autotest) | |
if result.exists? | |
case result.framework | |
when 'test-unit' | |
if result.has?('test-error') | |
growl @label + 'Cannot run some unit tests.', "#{result.get('test-error')} in #{result.get('test')}", 'error', 2, @@sticky_failure_notifications | |
elsif result.has?('test-failed') | |
growl @label + 'Some unit tests failed.', "#{result['test-failed']} of #{result.get('test-assertion')} in #{result.get('test')} failed", 'failed', 2, @@sticky_failure_notifications | |
else | |
growl @label + 'All unit tests passed.', "#{result.get('test-assertion')} in #{result.get('test')}", 'passed', -2 | |
end | |
when 'rspec' | |
if result.has?('example-failed') | |
growl @label + 'RSpec failed', "#{result.complete} \n #{result['example-failed']} of #{result.get('example')} failed", 'failed', 2, @@sticky_failure_notifications | |
elsif result.has?('example-pending') | |
growl @label + 'Some RSpec examples are pending.', "#{result['example-pending']} of #{result.get('example')} pending", 'pending', -1 | |
else | |
growl @label + 'All RSpec examples passed.', "#{result.get('example')}", 'passed', -2 | |
end | |
end | |
else | |
growl @label + 'Could not run tests.', '', 'error', 2, @@sticky_failure_notifications | |
end | |
@ran_tests = true | |
end | |
false | |
end | |
## | |
# Parse the Cucumber results and sent them to Growl. | |
Autotest.add_hook :ran_features do |autotest| | |
unless @@one_notification_per_run && @ran_features | |
result = Autotest::Result.new(autotest) | |
if result.exists? | |
case result.framework | |
when 'cucumber' | |
explanation = [] | |
if result.has?('scenario-undefined') || result.has?('step-undefined') | |
explanation << "#{result['scenario-undefined']} of #{result.get('scenario')} not defined" if result['scenario-undefined'] | |
explanation << "#{result['step-undefined']} of #{result.get('step')} not defined" if result['step-undefined'] | |
growl @label + 'Some Cucumber scenarios are not defined.', "#{explanation.join("\n")}", 'pending', -1 | |
elsif result.has?('scenario-failed') || result.has?('step-failed') | |
explanation << "#{result['scenario-failed']} of #{result.get('scenario')} failed" if result['scenario-failed'] | |
explanation << "#{result['step-failed']} of #{result.get('step')} failed" if result['step-failed'] | |
growl @label + 'Some Cucumber scenarios failed.', "#{explanation.join("\n")}", 'failed', 2, @@sticky_failure_notifications | |
elsif result.has?('scenario-pending') || result.has?('step-pending') | |
explanation << "#{result['scenario-pending']} of #{result.get('scenario')} pending" if result['scenario-pending'] | |
explanation << "#{result['step-pending']} of #{result.get('step')} pending" if result['step-pending'] | |
growl @label + 'Some Cucumber scenarios are pending.', "#{explanation.join("\n")}", 'pending', -1 | |
else | |
growl @label + 'All Cucumber features passed.', '', 'passed', -2 | |
end | |
end | |
else | |
growl @label + 'Could not run features.', '', 'error', 2, @@sticky_failure_notifications | |
end | |
@ran_features = true | |
end | |
false | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Autotest::Result | |
## | |
# Analyze test result lines and return the numbers in a hash. | |
def initialize(autotest) | |
@numbers = {} | |
lines = autotest.results.map {|s| s.gsub(/(\e.*?m|\n)/, '') } # remove escape sequences | |
@lines = autotest.results | |
lines.reject! {|line| !line.match(/\d+\s+(example|test|scenario|step)s?/) } # isolate result numbers | |
lines.each do |line| | |
prefix = nil | |
line.scan(/([1-9]\d*)\s(\w+)/) do |number, kind| | |
kind.sub!(/s$/, '') # singularize | |
kind.sub!(/failure/, 'failed') # homogenize | |
if prefix | |
@numbers["#{prefix}-#{kind}"] = number.to_i | |
else | |
@numbers[kind] = number.to_i | |
prefix = kind | |
end | |
end | |
end | |
end | |
## | |
# Determine the testing framework used. | |
def framework | |
case | |
when @numbers['test'] then 'test-unit' | |
when @numbers['example'] then 'rspec' | |
when @numbers['scenario'] then 'cucumber' | |
end | |
end | |
## | |
# Determine whether a result exists at all. | |
def exists? | |
[email protected]? | |
end | |
## | |
# Check whether a specific result is present. | |
def has?(kind) | |
@numbers.has_key?(kind) | |
end | |
## | |
# Get a plain result number. | |
def [](kind) | |
@numbers[kind] | |
end | |
## | |
# Get a labelled result number. The prefix is removed and the label pluralized if necessary. | |
def get(kind) | |
"#{@numbers[kind]} #{kind.sub(/^.*-/, '')}#{'s' if @numbers[kind] != 1 && !kind.match(/(ed|ing)$/)}" if @numbers[kind] | |
end | |
## | |
# Get the fatal error if any. | |
def fatal_error | |
end | |
def complete | |
@lines.reject! {|line| !line.match(/spec/) } | |
results = "" | |
for line in @lines | |
for word in line.split(" ") | |
if word.match(/spec/) && !word.match(/rspec/) | |
for piece in word.split("/") | |
if piece.match(/rb/) | |
splitted = piece.split(":") | |
fichero = splitted[0] | |
linea = splitted[1].match(/[0-9]+/).to_s | |
results += "#{fichero}:#{linea} \n \n" | |
end | |
end | |
end | |
end | |
end | |
return results | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment