lib/autowatchr.rb in autowatchr-0.0.0 vs lib/autowatchr.rb in autowatchr-0.1.0
- old
+ new
@@ -1,48 +1,121 @@
+require 'erb'
+
class Autowatchr
- attr_writer :ruby, :include, :lib_dir, :test_dir
+ class Config
+ attr_writer :command, :ruby, :include, :lib_dir, :test_dir, :lib_re,
+ :test_re, :failed_results_re, :completed_re, :failing_only, :run_suite
- def initialize(script, options = {})
- options.each_pair do |key, value|
- method = "#{key}="
- if self.respond_to?(method)
- self.send(method, value)
+ def initialize(options = {})
+ @failing_only = @run_suite = true
+
+ options.each_pair do |key, value|
+ method = "#{key}="
+ if self.respond_to?(method)
+ self.send(method, value)
+ end
end
end
- yield self if block_given?
- @script = script
- start_watching_files
- end
+ def command
+ @command ||= "<%= ruby %> -I<%= include %> <%= predicate %>"
+ end
- def ruby
- @ruby ||= "ruby"
- end
+ def ruby
+ @ruby ||= "ruby"
+ end
- def include
- @include ||= ".:#{self.lib_dir}:#{self.test_dir}"
- end
+ def include
+ @include ||= ".:#{self.lib_dir}:#{self.test_dir}"
+ end
- def lib_dir
- @lib_dir ||= "lib"
+ def lib_dir
+ @lib_dir ||= "lib"
+ end
+
+ def test_dir
+ @test_dir ||= "test"
+ end
+
+ def lib_re
+ @lib_re ||= '^%s.*/.*\.rb$' % self.lib_dir
+ end
+
+ def test_re
+ @test_re ||= '^%s.*/test_.*\.rb$' % self.test_dir
+ end
+
+ def failed_results_re
+ @failed_results_re ||= /^\s+\d+\) (?:Failure|Error):\n(.*?)\((.*?)\)/
+ end
+
+ def completed_re
+ @completed_re ||= /\d+ tests, \d+ assertions, \d+ failures, \d+ errors/
+ end
+
+ def failing_only
+ @failing_only
+ end
+
+ def run_suite
+ @run_suite
+ end
+
+ def eval_command(predicate)
+ ERB.new(self.command).result(binding)
+ end
end
- def test_dir
- @test_dir ||= "test"
+ attr_reader :config
+
+ def initialize(script, options = {})
+ @config = Config.new(options)
+ yield @config if block_given?
+ @script = script
+ @test_files = []
+ @failed_tests = {}
+
+ discover_files
+ run_all_tests
+ start_watching_files
end
def run_lib_file(file)
- md = file.match(%r{^#{@lib_dir}#{File::SEPARATOR}?(.+)$})
+ md = file.match(%r{^#{@config.lib_dir}#{File::SEPARATOR}?(.+)$})
parts = md[1].split(File::SEPARATOR)
parts[-1] = "test_#{parts[-1]}"
- file = "#{@test_dir}/" + File.join(parts)
+ file = "#{@config.test_dir}/" + File.join(parts)
run_test_file(file)
end
- def run_test_file(file)
- cmd = "%s -I%s %s" % [ self.ruby, self.include, file ]
+ def run_test_file(files)
+ files = [files] unless files.is_a?(Array)
+ passing = []
+ commands = []
+ files.each do |file|
+ tests = @failed_tests[file]
+ if tests && !tests.empty?
+ predicate = %!#{file} -n "/^(#{tests.join("|")})$/"!
+ commands << @config.eval_command(predicate)
+ else
+ passing << file
+ end
+ end
+
+ if !passing.empty?
+ predicate = if passing.length > 1
+ "-e \"%w[#{passing.join(" ")}].each { |f| require f }\""
+ else
+ passing[0]
+ end
+ commands.unshift(@config.eval_command(predicate))
+ end
+
+ cmd = commands.join("; ")
+ puts cmd
+
# straight outta autotest
results = []
line = []
open("| #{cmd}", "r") do |f|
until f.eof? do
@@ -50,20 +123,70 @@
putc c
line << c
if c == ?\n then
results << if RUBY_VERSION >= "1.9" then
line.join
- else
- line.pack "c*"
- end
+ else
+ line.pack "c*"
+ end
line.clear
end
end
end
+ handle_results(results.join, files)
end
+ def classname_to_path(s)
+ File.join(@config.test_dir, underscore(s)+".rb")
+ end
+
private
+ def discover_files
+ @test_files = Dir.glob("#{@config.test_dir}/**/*").grep(/#{@config.test_re}/)
+ end
+
+ def run_all_tests
+ run_test_file(@test_files)
+ end
+
def start_watching_files
- @script.watch("^#{self.test_dir}.*/test_.*\.rb") { |md| run_test_file(md[0]) }
- @script.watch("^#{self.lib_dir}.*/.*\.rb") { |md| run_lib_file(md[0]) }
+ @script.watch(@config.test_re) { |md| run_test_file(md[0]) }
+ @script.watch(@config.lib_re) { |md| run_lib_file(md[0]) }
+ end
+
+ def handle_results(results, files_ran)
+ return if !@config.failing_only
+ num_previously_failed = @failed_tests.length
+
+ failed = results.scan(@config.failed_results_re)
+ completed = results =~ @config.completed_re
+
+ previously_failed = @failed_tests.keys & files_ran
+ failed.each do |(test_name, class_name)|
+ key = classname_to_path(class_name)
+ if files_ran.include?(key)
+ @failed_tests[key] ||= []
+ @failed_tests[key] << test_name
+ previously_failed.delete(key)
+ else
+ puts "Couldn't map class to file: #{class_name}"
+ end
+ end
+
+ previously_failed.each do |file|
+ @failed_tests.delete(file)
+ end
+
+ if @config.run_suite && @failed_tests.empty? && num_previously_failed > 0
+ run_all_tests
+ end
+ end
+
+ # File vendor/rails/activesupport/lib/active_support/inflector.rb, line 206
+ def underscore(camel_cased_word)
+ camel_cased_word.to_s.gsub(/::/, '/').
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
+ tr("-", "_").
+ downcase
end
end