166 lines
5.1 KiB
Ruby
166 lines
5.1 KiB
Ruby
# Adapted from Autotest::Rails, RSpec's autotest class, as well as merb-core's.
|
|
require 'autotest'
|
|
|
|
class RspecCommandError < StandardError; end
|
|
|
|
# This class maps your application's structure so Autotest can understand what
|
|
# specs to run when files change.
|
|
#
|
|
# Fixtures are _not_ covered by this class. If you change a fixture file, you
|
|
# will have to run your spec suite manually, or, better yet, provide your own
|
|
# Autotest map explaining how your fixtures are set up.
|
|
class Autotest::MerbRspec < Autotest
|
|
def initialize
|
|
super
|
|
|
|
# Ignore any happenings in these directories
|
|
add_exception %r%^\./(?:doc|log|public|tmp|\.git|\.hg|\.svn|framework|gems|schema|\.DS_Store|autotest|bin|.*\.sqlite3)%
|
|
# Ignore SCM directories and custom Autotest mappings
|
|
%w[.svn .hg .git .autotest].each { |exception| add_exception(exception) }
|
|
|
|
# Ignore any mappings that Autotest may have already set up
|
|
clear_mappings
|
|
|
|
# Anything in /lib could have a spec anywhere, if at all. So, look for
|
|
# files with roughly the same name as the file in /lib
|
|
add_mapping %r%^lib\/(.*)\.rb% do |_, m|
|
|
files_matching %r%^spec\/#{m[1]}%
|
|
end
|
|
|
|
add_mapping %r%^spec/(spec_helper|shared/.*)\.rb$% do
|
|
all_specs
|
|
end
|
|
|
|
# Changing a spec will cause it to run itself
|
|
add_mapping %r%^spec/.*\.rb$% do |filename, _|
|
|
filename
|
|
end
|
|
|
|
# Any change to a model will cause it's corresponding test to be run
|
|
add_mapping %r%^app/models/(.*)\.rb$% do |_, m|
|
|
spec_for(m[1], 'model')
|
|
end
|
|
|
|
# Any change to global_helpers will result in all view and controller
|
|
# tests being run
|
|
add_mapping %r%^app/helpers/global_helpers\.rb% do
|
|
files_matching %r%^spec/(views|controllers|helpers)/.*_spec\.rb$%
|
|
end
|
|
|
|
# Any change to a helper will cause its spec to be run
|
|
add_mapping %r%^app/helpers/((.*)_helper(s)?)\.rb% do |_, m|
|
|
spec_for(m[1], 'helper')
|
|
end
|
|
|
|
# Changes to a view cause its spec to be run
|
|
add_mapping %r%^app/views/(.*)/% do |_, m|
|
|
spec_for(m[1], 'view')
|
|
end
|
|
|
|
# Changes to a controller result in its corresponding spec being run. If
|
|
# the controller is the exception or application controller, all
|
|
# controller specs are run.
|
|
add_mapping %r%^app/controllers/(.*)\.rb$% do |_, m|
|
|
if ["application", "exception"].include?(m[1])
|
|
files_matching %r%^spec/controllers/.*_spec\.rb$%
|
|
else
|
|
spec_for(m[1], 'controller')
|
|
end
|
|
end
|
|
|
|
# If a change is made to the router, run controller, view and helper specs
|
|
add_mapping %r%^config/router.rb$% do
|
|
files_matching %r%^spec/(controllers|views|helpers)/.*_spec\.rb$%
|
|
end
|
|
|
|
# If any of the major files governing the environment are altered, run
|
|
# everything
|
|
add_mapping %r%^config/(init|rack|environments/test).*\.rb|database\.yml% do
|
|
all_specs
|
|
end
|
|
end
|
|
|
|
def failed_results(results)
|
|
results.scan(/^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m)
|
|
end
|
|
|
|
def handle_results(results)
|
|
@failures = failed_results(results)
|
|
@files_to_test = consolidate_failures(@failures)
|
|
@files_to_test.empty? && !$TESTING ? hook(:green) : hook(:red)
|
|
@tainted = !@files_to_test.empty?
|
|
end
|
|
|
|
def consolidate_failures(failed)
|
|
filters = Hash.new { |h,k| h[k] = [] }
|
|
failed.each do |spec, failed_trace|
|
|
if f = test_files_for(failed).find { |f| f =~ /spec\// }
|
|
filters[f] << spec
|
|
break
|
|
end
|
|
end
|
|
filters
|
|
end
|
|
|
|
def make_test_cmd(specs_to_runs)
|
|
[
|
|
ruby,
|
|
"-S",
|
|
spec_command,
|
|
add_options_if_present,
|
|
files_to_test.keys.flatten.join(' ')
|
|
].join(' ')
|
|
end
|
|
|
|
def add_options_if_present
|
|
File.exist?("spec/spec.opts") ? "-O spec/spec.opts " : ""
|
|
end
|
|
|
|
# Finds the proper spec command to use. Precendence is set in the
|
|
# lazily-evaluated method spec_commands. Alias + Override that in
|
|
# ~/.autotest to provide a different spec command then the default
|
|
# paths provided.
|
|
def spec_command(separator=File::ALT_SEPARATOR)
|
|
unless defined?(@spec_command)
|
|
@spec_command = spec_commands.find { |cmd| File.exists?(cmd) }
|
|
|
|
raise RspecCommandError, "No spec command could be found" unless @spec_command
|
|
|
|
@spec_command.gsub!(File::SEPARATOR, separator) if separator
|
|
end
|
|
@spec_command
|
|
end
|
|
|
|
# Autotest will look for spec commands in the following
|
|
# locations, in this order:
|
|
#
|
|
# * default spec bin/loader installed in Rubygems
|
|
# * any spec command found in PATH
|
|
def spec_commands
|
|
[File.join(Config::CONFIG['bindir'], 'spec'), 'spec']
|
|
end
|
|
|
|
private
|
|
|
|
# Runs +files_matching+ for all specs
|
|
def all_specs
|
|
files_matching %r%^spec/.*_spec\.rb$%
|
|
end
|
|
|
|
# Generates a path to some spec given its kind and the match from a mapping
|
|
#
|
|
# ==== Arguments
|
|
# match<String>:: the match from a mapping
|
|
# kind<String>:: the kind of spec that the match represents
|
|
#
|
|
# ==== Returns
|
|
# String
|
|
#
|
|
# ==== Example
|
|
# > spec_for('post', :view')
|
|
# => "spec/views/post_spec.rb"
|
|
def spec_for(match, kind)
|
|
File.join("spec", kind + 's', "#{match}_spec.rb")
|
|
end
|
|
end
|