Skip to content

Instantly share code, notes, and snippets.

@mmrwoods
Created October 27, 2010 06:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mmrwoods/648573 to your computer and use it in GitHub Desktop.
Save mmrwoods/648573 to your computer and use it in GitHub Desktop.
Use autotest to run rails tests/specs impacted by changes shown by git-diff
#!/usr/bin/env ruby
require 'rubygems'
require 'autotest'
require 'optparse'
# exit if we're not in a git repository
exit $?.exitstatus unless system('git diff --name-only > /dev/null')
# make sure we're in the root path for the repository
loop do
begin
Dir.entries('.git')
break
rescue SystemCallError
Dir.chdir('..')
next
end
end
options = {:fast => false, :diff => 'HEAD', :trace => false}
OptionParser.new do |opts|
opts.banner = "Usage: ./script/gittest [options]"
opts.on("-f", "--fast", "Fast mode - skips preparation of test db") do |o|
options[:fast] = o
end
opts.on("-d MANDATORY", "--diff MANDATORY", "Commit argument for git diff command used to check for new or modified files (defaults to HEAD)") do |o|
options[:diff] = o
end
opts.on("-t", "--trace", "Enable trace option when calling rake tasks to prepare test db") do |o|
options[:trace] = o
end
end.parse!
# prepare db if fast start not switched on
unless options[:fast]
puts "Preparing test database..."
puts "(You can use the -f switch to skip this in future)"
rake_options = options[:trace] ? '--trace' : ''
system "rake db:migrate RAILS_ENV=test #{$rake_options} > /dev/null"
system "rake db:test:prepare #{$rake_options} > /dev/null"
end
# autotest options
$f = true # never run the entire test/spec suite on startup
$v = false
$h = false
$q = false
$DEBUG = false
$help = false
# use ansi colors to highlight test/spec passes, failures and errors
COLORS = { :red => 31, :green => 32, :yellow => 33 }
# get a list of new or modified files according to git (using terminal commands is faster than using a ruby git library)
new_or_modified_files = `git diff --name-only #{options[:diff]}`.split("\n").uniq
if new_or_modified_files.size == 0
puts "No modified files, exiting"
exit
end
msg = "#{new_or_modified_files.size} new or modified file"
msg << "s" unless new_or_modified_files.size == 1
puts msg + ":"
new_or_modified_files.each {|f| puts "\t#{f}"}
at = Autotest.new
# Note: the initialize hook is normally called within Autotest#run
at.hook :initialize
at.reset
at.find_files # must populate the known files for autotest, otherwise Autotest#files_matching will always return nil
# this isn't pretty, but it will probably be reliable enough (can't see any good reason for renaming that particular instance variable, IMO it should be accessible anyway)
test_mappings = at.instance_eval { @test_mappings }
# find files to test
files_to_test = at.new_hash_of_arrays
new_or_modified_files.each do |f|
next if f =~ at.exceptions # skip exceptions
result = test_mappings.find { |file_re, ignored| f =~ file_re }
unless result.nil?
[result.last.call(f, $~)].flatten.each {|match| files_to_test[match] if File.exist?(match)}
end
end
# exit if no files to test
puts "No matching files to test, exiting" and exit if files_to_test.empty?
msg = "#{files_to_test.size} file"
msg << "s" unless files_to_test.size == 1
msg << " to test"
puts msg + ":"
puts "\t" + files_to_test.map{|k,v| k}.sort.join("\n\t")
puts "Press ENTER to continue, or CTRL+C to quit"
begin
$stdin.gets # note: Kernel#gets assumes that ARGV contains a list of files from which to read next line
rescue Interrupt
exit 1
end
puts "Running tests and specs, please wait..."
cmd = at.make_test_cmd(files_to_test)
at.hook :run_command
# copied from Autotest#run_tests and updated to use ansi colours in TURN enabled test output and specs run with the format option set to specdoc
old_sync = $stdout.sync
$stdout.sync = true
results = []
line = []
begin
open("| #{cmd}", "r") do |f|
until f.eof? do
c = f.getc or break
# putc c
line << c
if c == ?\n then
str = if RUBY_VERSION >= "1.9" then
line.join
else
line.pack "c*"
end
results << str
line.clear
if str.match(/(PASS|FAIL|ERROR)$/)
# test output
case $1
when 'PASS' ; color = :green
when 'FAIL' ; color = :red
when 'ERROR' ; color = :yellow
end
print "\e[#{COLORS[color]}m" + str + "\e[0m"
elsif str.match(/^\- /)
# spec output
if str.match(/^\- .*(ERROR|FAILED) \- [0-9]+/)
color = $1 == 'FAILED' ? :red : :yellow
print "\e[#{COLORS[color]}m" + str + "\e[0m"
else
print "\e[#{COLORS[:green]}m" + str + "\e[0m"
end
else
print str
end
end
end
end
ensure
$stdout.sync = old_sync
end
at.handle_results(results.join)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment