1 # Adapted from Autotest::Rails, RSpec's autotest class, as well as merb-core's.
4 class RspecCommandError < StandardError; end
6 class Autotest::MerbRspec < Autotest
8 # +model_tests_dir+:: the directory to find model-centric tests
9 # +controller_tests_dir+:: the directory to find controller-centric tests
10 # +view_tests_dir+:: the directory to find view-centric tests
11 # +fixtures_dir+:: the directory to find fixtures in
12 attr_accessor :model_tests_dir, :controller_tests_dir, :view_tests_dir, :fixtures_dir
14 def initialize # :nodoc:
17 initialize_test_layout
19 # Ignore any happenings in these directories
20 add_exception %r%^\./(?:doc|log|public|tmp)%
22 # Ignore any mappings that Autotest may have already set up
25 # Any changes to a file in the root of the 'lib' directory will run any
26 # model test with a corresponding name.
27 add_mapping %r%^lib\/.*\.rb% do |filename, _|
28 files_matching %r%#{model_test_for(filename)}$%
31 add_mapping %r%^spec/(spec_helper|shared/.*)\.rb$% do
32 files_matching %r%^spec/.*_spec\.rb$%
35 # Any changes to a fixture will run corresponding view, controller and
37 add_mapping %r%^#{fixtures_dir}/(.*)s.yml% do |_, m|
40 controller_test_for(m[1]),
45 # Any change to a test or spec will cause it to be run
46 add_mapping %r%^spec/(unit|models|integration|controllers|views|functional)/.*rb$% do |filename, _|
50 # Any change to a model will cause it's corresponding test to be run
51 add_mapping %r%^app/models/(.*)\.rb$% do |_, m|
55 # Any change to the global helper will result in all view and controller
57 add_mapping %r%^app/helpers/global_helpers.rb% do
58 files_matching %r%^spec/(views|functional|controllers)/.*_spec\.rb$%
61 # Any change to a helper will run it's corresponding view and controller
62 # tests, unless the helper is the global helper. Changes to the global
63 # helper run all view and controller tests.
64 add_mapping %r%^app/helpers/(.*)_helper(s)?.rb% do |_, m|
65 if m[1] == "global" then
66 files_matching %r%^spec/(views|functional|controllers)/.*_spec\.rb$%
70 controller_test_for(m[1])
75 # Changes to views result in their corresponding view and controller test
77 add_mapping %r%^app/views/(.*)/% do |_, m|
80 controller_test_for(m[1])
84 # Changes to a controller result in its corresponding test being run. If
85 # the controller is the exception or application controller, all
86 # controller tests are run.
87 add_mapping %r%^app/controllers/(.*)\.rb$% do |_, m|
88 if ["application", "exception"].include?(m[1])
89 files_matching %r%^spec/(controllers|views|functional)/.*_spec\.rb$%
91 controller_test_for(m[1])
95 # If a change is made to the router, run all controller and view tests
96 add_mapping %r%^config/router.rb$% do # FIX
97 files_matching %r%^spec/(controllers|views|functional)/.*_spec\.rb$%
100 # If any of the major files governing the environment are altered, run
102 add_mapping %r%^spec/spec_helper.rb|config/(init|rack|environments/test.rb|database.yml)% do # FIX
103 files_matching %r%^spec/(unit|models|controllers|views|functional)/.*_spec\.rb$%
107 def failed_results(results)
108 results.scan(/^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m)
111 def handle_results(results)
112 @failures = failed_results(results)
113 @files_to_test = consolidate_failures @failures
115 if @files_to_test.empty?
121 @tainted = true unless @files_to_test.empty?
124 def consolidate_failures(failed)
125 filters = Hash.new { |h,k| h[k] = [] }
126 failed.each do |spec, failed_trace|
127 find_files.keys.select { |f| f =~ /spec\// }.each do |f|
128 if failed_trace =~ Regexp.new(f)
137 def make_test_cmd(files_to_test)
142 add_options_if_present,
143 files_to_test.keys.flatten.join(' ')
147 def add_options_if_present
148 File.exist?("spec/spec.opts") ? "-O spec/spec.opts " : ""
151 # Finds the proper spec command to use. Precendence is set in the
152 # lazily-evaluated method spec_commands. Alias + Override that in
153 # ~/.autotest to provide a different spec command then the default
155 def spec_command(separator=File::ALT_SEPARATOR)
156 unless defined? @spec_command then
157 @spec_command = spec_commands.find { |cmd| File.exists? cmd }
159 raise RspecCommandError, "No spec command could be found!" unless @spec_command
161 @spec_command.gsub!(File::SEPARATOR, separator) if separator
166 # Autotest will look for spec commands in the following
167 # locations, in this order:
169 # * default spec bin/loader installed in Rubygems
170 # * any spec command found in PATH
172 [ File.join(Config::CONFIG['bindir'], 'spec'), 'spec' ]
177 # Determines the paths we can expect tests or specs to reside, as well as
178 # corresponding fixtures.
179 def initialize_test_layout
180 self.model_tests_dir = "spec/models"
181 self.controller_tests_dir = "spec/controllers"
182 self.view_tests_dir = "spec/views"
183 self.fixtures_dir = "spec/fixtures"
186 # Given a filename and the test type, this method will return the
187 # corresponding test's or spec's name.
190 # +filename+<String>:: the file name of the model, view, or controller
191 # +kind_of_test+<Symbol>:: the type of test we that we should run
194 # String:: the name of the corresponding test or spec
198 # > test_for("user", :model)
200 # > test_for("login", :controller)
201 # => "login_controller_test.rb"
202 # > test_for("form", :view)
203 # => "form_view_spec.rb" # If you're running a RSpec-like suite
204 def test_for(filename, kind_of_test) # :nodoc:
206 name << kind_of_test.to_s if kind_of_test == :view
208 return name.join("_") + ".rb"
211 def model_test_for(filename)
212 [model_tests_dir, test_for(filename, :model)].join("/")
215 def controller_test_for(filename)
216 [controller_tests_dir, test_for(filename, :controller)].join("/")
219 def view_test_for(filename)
220 [view_tests_dir, test_for(filename, :view)].join("/")