├── .gitignore ├── Gemfile ├── Guardfile ├── History.txt ├── LICENSE ├── README.md ├── extra └── gj-rspec ├── guard-jruby-rspec.gemspec ├── lib ├── guard-jruby-rspec.rb └── guard │ ├── jruby-rspec.rb │ └── jruby-rspec │ ├── containment.rb │ ├── formatters │ └── notification_rspec.rb │ ├── inspector.rb │ ├── reloaders.rb │ ├── runner.rb │ ├── templates │ └── Guardfile │ └── version.rb └── spec ├── guard ├── jruby-rspec │ ├── containment_spec.rb │ ├── reloaders_spec.rb │ └── runner_spec.rb └── jruby-rspec_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .guard-jruby-spec 3 | Gemfile.lock -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in guard-jruby-rspec.gemspec 4 | gemspec -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard 'jruby-rspec' 5 | 6 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | == 0.2.0 2 | 3 | - Use Rails 3.2+ class reloading when available 4 | - Refactoring of loading into Containment to better handle errors -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Joe Kutner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # guard-jruby-rspec 2 | 3 | This guard extention allows you to run all of your specs on JRuby without the initial start up cost. It loads all of your application files in advance, and reloads any that change. That way, when you run RSpec, the JVM is already running, and your files have already been required. 4 | 5 | Most of the config options available to `guard-rspec` work with this extension too. 6 | 7 | ## How to Use On-Demand mode 8 | 9 | Just add this to your guard file: 10 | 11 | interactor :simple 12 | guard 'jruby-rspec', :spec_paths => ["spec"] 13 | 14 | Then run `guard` like this (probably with Bundler): 15 | 16 | $ bundle exec guard 17 | Using polling (Please help us to support your system better than that). 18 | The signal USR1 is in use by the JVM and will not work correctly on this platform 19 | Guard could not detect any of the supported notification libraries. 20 | Guard is now watching at '~/myapp' 21 | Guard::JRuby::RSpec is running, with RSpec! 22 | ....... 23 | 24 | Finished in 0.735 seconds 25 | 7 examples, 0 failures 26 | > 27 | 28 | The first time guard starts up, it will run all of your specs in order to bootstrap the runtime. This first run will be as slow as any other run on JRuby. 29 | 30 | Once you change some files, and press return at the guard prompt to rerun your specs. You'll notice it's a lot faster than running `rspec` from the command line. 31 | 32 | ## How to Use Autorun mode 33 | 34 | Add something like this to your guard file (alternatives are in the template file): 35 | 36 | interactor :simple 37 | guard 'jruby-rspec' do 38 | watch(%r{^spec/.+_spec\.rb$}) 39 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 40 | watch('spec/spec_helper.rb') { "spec" } 41 | end 42 | 43 | Proceed as in on-demand mode. 44 | 45 | ## Code Reloading 46 | 47 | Since JRuby cannot fork, guard-jruby-rspec reloads code with `load` on each changed path, which can potentially cause weird side side effects or errors. 48 | 49 | Rails 3.2+ projects also use the Rails reloader to unload classes on each run. 50 | 51 | Loading classes more than once does not work if the class definition is not idempotent. 52 | When using this gem on a non Rails 3.2+ project, you may want to unload these classes manually if you get new errors on the 2nd run: 53 | 54 | Pass in custom reloaders as an option: 55 | 56 | unload_my_class = lambda { |changed_paths| Object.send :remove_const, 'MyClass' } 57 | reload_factory_girl = lambda { |changed_paths| FactoryGirl.reload } # Already included by default when FactoryGirl is loaded 58 | guard 'jruby-rspec', :custom_reloaders => [unload_my_class, reload_factory_girl] do 59 | ... 60 | end 61 | 62 | ## Using CLI Options 63 | 64 | The format that `guard-jruby-rspec` expects CLI options to be in is a little different than what `guard-rspec` exepcts. Here is an example: 65 | 66 | interactor :simple 67 | guard "jruby-rspec", :cli => ["-c", "-t~slow"] 68 | 69 | The CLI options should be an Array containing a number of strings. Each string should be a flag and an option value with no space between the flag and the value. 70 | 71 | ## TODO 72 | 73 | + Autorun specs like guard-rspec (want to integrate with guard-rspec so as to not duplicate all of it's logic). 74 | 75 | + Allow for extra rspec options 76 | 77 | + Fix the way guard uses stdin so its not flaky on JRuby 78 | 79 | + Work out the kinks in gj-rspec script so that specs can be run in main terminal. 80 | 81 | ## Thank You 82 | 83 | Thank you to the authors of `guard-rspec`. I'm piggybacking off of the hard work done by [Thibaud Guillaume-Gentil](https://github.com/thibaudgg) and others! 84 | 85 | ## Author 86 | 87 | [@codefinger](http://twitter.com/#!/codefinger) 88 | -------------------------------------------------------------------------------- /extra/gj-rspec: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # TODO don't run if .guard-jruby-rspec doesn't exist 4 | # instead, warn that guard must be (re)started 5 | 6 | touch .guard-jruby-rspec-pipe 7 | rm .guard-jruby-rspec 8 | 9 | # todo: also put the cmd args in here 10 | echo $$ > .guard-jruby-rspec 11 | 12 | #tail -n +0 -f .guard-jruby-rspec-pipe | { sed "/EOF/ q" && kill $$ ;} 13 | tail -F .guard-jruby-rspec-pipe -------------------------------------------------------------------------------- /guard-jruby-rspec.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path('../lib', __FILE__) 3 | require 'guard/jruby-rspec/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'guard-jruby-rspec' 7 | s.version = Guard::JRubyRSpecVersion::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ['Joe Kutner'] 10 | s.email = ['jpkutner@gmail.com'] 11 | s.homepage = 'http://rubygems.org/gems/guard-jruby-rspec' 12 | s.summary = 'Guard gem for JRuby RSpec' 13 | s.description = 'Guard::JRubyRSpec keeps a warmed up JVM ready to run your specs.' 14 | 15 | s.required_rubygems_version = '>= 1.3.6' 16 | s.rubyforge_project = 'guard-jruby-rspec' 17 | 18 | s.add_dependency 'guard', '>= 0.10.0', '< 2.0.0' 19 | s.add_dependency 'guard-rspec', '>= 0.7.3', '< 4.0.0' 20 | 21 | s.add_development_dependency 'rspec', '~> 2.7' 22 | 23 | s.files = Dir.glob('{lib}/**/*') + %w[LICENSE README.md] 24 | s.require_path = 'lib' 25 | s.license = "MIT" 26 | end 27 | -------------------------------------------------------------------------------- /lib/guard-jruby-rspec.rb: -------------------------------------------------------------------------------- 1 | require 'guard/jruby-rspec' -------------------------------------------------------------------------------- /lib/guard/jruby-rspec.rb: -------------------------------------------------------------------------------- 1 | require 'guard' 2 | require 'guard/guard' 3 | require 'guard/rspec' 4 | require 'guard/jruby-rspec/reloaders' 5 | 6 | module Guard 7 | class JRubyRSpec < ::Guard::RSpec 8 | autoload :Runner, 'guard/jruby-rspec/runner' 9 | autoload :Inspector, 'guard/jruby-rspec/inspector' 10 | 11 | def initialize(watchers = [], options = {}) 12 | @options = { 13 | :all_after_pass => true, 14 | :all_on_start => true, 15 | :keep_failed => true, 16 | :spec_paths => ["spec"], 17 | :spec_file_suffix => "_spec.rb", 18 | :run_all => {}, 19 | :monitor_file => ".guard-jruby-rspec", 20 | :custom_reloaders => [] 21 | }.merge(options) 22 | @last_failed = false 23 | @failed_paths = [] 24 | 25 | default_watchers = [Watcher.new(@monitor)] 26 | if @custom_watchers.nil? or @custom_watchers.empty? 27 | default_watchers << 28 | Watcher.new(%r{^(.+)\.rb$}) << 29 | Watcher.new(%r{^(.+)\.(erb|haml)$}) 30 | else 31 | watchers.each do |w| 32 | default_watchers << Watcher.new(w.pattern) 33 | end 34 | end 35 | 36 | @custom_watchers = watchers 37 | 38 | # touch the monitor file (lets the gjrspec know we're here) 39 | #File.open(@monitor, "w") {} 40 | 41 | # ideally we would bypass the Guard::RSpec initializer 42 | super(default_watchers, @options) 43 | 44 | @inspector = Inspector.new(@options) 45 | @runner = Runner.new(@options) 46 | @reloaders = set_up_reloaders(@options) 47 | end 48 | 49 | # Call once when guard starts 50 | def start 51 | UI.info "Guard::JRuby::RSpec is running, with RSpec!" 52 | run_all if @options[:all_on_start] 53 | end 54 | 55 | def run_all 56 | unload_previous_examples 57 | super 58 | end 59 | 60 | def run_on_changes(raw_paths) 61 | unload_previous_examples 62 | @reloaders.reload(raw_paths) 63 | 64 | unless @custom_watchers.nil? or @custom_watchers.empty? 65 | paths = [] 66 | 67 | raw_paths.each do |p| 68 | @custom_watchers.each do |w| 69 | if (m = w.match(p)) 70 | paths << (w.action.nil? ? p : w.call_action(m)) 71 | end 72 | end 73 | end 74 | super(paths.flatten) 75 | end 76 | end 77 | # Guard 1.1 renamed run_on_change to run_on_changes 78 | alias_method :run_on_change, :run_on_changes 79 | 80 | def reload_rails(*) 81 | if defined? ::ActionDispatch::Reloader 82 | ActionDispatch::Reloader.cleanup! 83 | ActionDispatch::Reloader.prepare! 84 | end 85 | end 86 | 87 | def reload_factory_girl(*) 88 | FactoryGirl.reload if defined? ::FactoryGirl 89 | end 90 | 91 | def reload_paths(paths) 92 | paths.reject {|p| p.end_with?(@options[:spec_file_suffix])}.each do |p| 93 | if File.exists?(p) 94 | if p == @options[:monitor_file] 95 | # begin 96 | # pidfile = open(@options[:monitor_file], "r+") 97 | # pid = pidfile.read 98 | 99 | # run_all 100 | 101 | # system("kill #{pid}") if (pid and !pid.empty?) 102 | # ensure 103 | # @runner.cleanup 104 | # end 105 | else 106 | # reload the file 107 | Containment.new.protect do 108 | load p 109 | end 110 | end 111 | end 112 | end 113 | end 114 | 115 | private 116 | 117 | def set_up_reloaders(options) 118 | reloaders = Reloaders.new 119 | reloader_methods = [:reload_rails, :reload_paths, :reload_factory_girl] 120 | reloader_procs = reloader_methods.map { |name| method(name) } 121 | reloader_procs += options[:custom_reloaders] 122 | reloader_procs.each { |reloader| reloaders.register &reloader } 123 | 124 | reloaders 125 | end 126 | 127 | def unload_previous_examples 128 | ::RSpec.configuration.reset 129 | ::RSpec.world.reset 130 | end 131 | end 132 | end 133 | 134 | -------------------------------------------------------------------------------- /lib/guard/jruby-rspec/containment.rb: -------------------------------------------------------------------------------- 1 | module Guard 2 | class JRubyRSpec 3 | class Containment 4 | def initialize(options = {}) 5 | @error_handler = options.fetch(:error_handler, method(:output_as_guard_error)) 6 | end 7 | 8 | def protect 9 | yield 10 | rescue Exception => e 11 | error_handler.call e 12 | throw :task_has_failed 13 | end 14 | 15 | private 16 | 17 | attr_reader :error_handler 18 | 19 | def output_as_guard_error(exception) 20 | UI.error $!.message 21 | UI.error $!.backtrace.join "\n" 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/guard/jruby-rspec/formatters/notification_rspec.rb: -------------------------------------------------------------------------------- 1 | require "guard/rspec/formatter" 2 | 3 | superclass = if Guard::RSpec::Formatter.instance_of? Module # guard-rspec-1.x 4 | Object 5 | elsif Guard::RSpec::Formatter.instance_of? Class # guard-rspec-2.x 6 | Guard::RSpec::Formatter 7 | else 8 | fail 'Guard::RSpec::Formatter is neither class nor module' 9 | end 10 | 11 | class Guard::JRubyRSpec::Formatter::NotificationRSpec < superclass 12 | include Guard::RSpec::Formatter if Guard::RSpec::Formatter.instance_of? Module 13 | 14 | def dump_summary(duration, total, failures, pending) 15 | message = guard_message(total, failures, pending, duration) 16 | image = guard_image(failures, pending) 17 | notify(message, image) 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/guard/jruby-rspec/inspector.rb: -------------------------------------------------------------------------------- 1 | require 'guard/rspec/inspector' 2 | 3 | module Guard 4 | class JRubyRSpec 5 | class Inspector < ::Guard::RSpec::Inspector 6 | 7 | end 8 | end 9 | end -------------------------------------------------------------------------------- /lib/guard/jruby-rspec/reloaders.rb: -------------------------------------------------------------------------------- 1 | class Reloaders 2 | def initialize 3 | @reloaders = [] 4 | end 5 | 6 | # Add a reloader to be called on reload 7 | def register(options = {}, &block) 8 | if options[:prepend] 9 | @reloaders.unshift block 10 | else 11 | @reloaders << block 12 | end 13 | end 14 | 15 | def reload(paths = []) 16 | @reloaders.each do |reloader| 17 | reloader.call(paths) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/guard/jruby-rspec/runner.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'guard/jruby-rspec/containment' 3 | require 'guard/jruby-rspec/formatters/notification_rspec' 4 | 5 | module Guard 6 | class JRubyRSpec 7 | class Runner 8 | 9 | def initialize(options = {}) 10 | @options = { 11 | :cli => [], 12 | :notification => true 13 | }.merge(options) 14 | 15 | @pipefile = options[:pipefile] 16 | @pipefile ||= ".guard-jruby-rspec-pipe" 17 | cleanup 18 | end 19 | 20 | def cleanup 21 | File.delete(@pipefile) if File.exists?(@pipefile) 22 | end 23 | 24 | def run(paths, options = {}) 25 | return false if paths.empty? 26 | 27 | message = options[:message] || "Running: #{paths.join(' ')}" 28 | UI.info(message, :reset => true) 29 | 30 | # it might be a problem to run Rspec within this runtime. Might have to create an 31 | # embedded jruby. 32 | if File.exists?(@pipefile) 33 | raise "not supported yet" 34 | # instead of writing to the pipefile, we should probably use a 35 | # formatter and write to /dev/null 36 | # orig_stdout = $stdout.clone 37 | # orig_stderr = $stderr.clone 38 | # begin 39 | # $stdout.reopen(@pipefile, "w") 40 | # $stderr.reopen(@pipefile, "w") 41 | # ::RSpec::Core::Runner.run(paths) 42 | # ensure 43 | # $stdout.reopen(orig_stdout) 44 | # $stderr.reopen(orig_stderr) 45 | # end 46 | else 47 | orig_configuration = ::RSpec.configuration 48 | Containment.new.protect do 49 | ::RSpec::Core::Runner.run(rspec_arguments(paths, @options)) 50 | end 51 | ::RSpec.instance_variable_set(:@configuration, orig_configuration) 52 | end 53 | end 54 | 55 | def parsed_or_default_formatter 56 | @parsed_or_default_formatter ||= begin 57 | file_name = "#{Dir.pwd}/.rspec" 58 | parsed_formatter = if File.exist?(file_name) 59 | formatters = File.read(file_name).scan(formatter_regex).flatten 60 | formatters.map { |formatter| "-f#{formatter}" }.join(' ') 61 | end 62 | 63 | parsed_formatter.nil? || parsed_formatter.empty? ? '-fprogress' : parsed_formatter 64 | end 65 | end 66 | 67 | private 68 | 69 | def rspec_arguments(paths, options) 70 | arg_parts = [] 71 | arg_parts.concat(options[:cli]) if options[:cli] 72 | if @options[:notification] 73 | arg_parts << parsed_or_default_formatter unless options[:cli] =~ formatter_regex 74 | arg_parts << "-fGuard::JRubyRSpec::Formatter::NotificationRSpec" 75 | arg_parts << "-o/dev/null" 76 | end 77 | #arg_parts << "--failure-exit-code #{FAILURE_EXIT_CODE}" if failure_exit_code_supported? 78 | arg_parts.concat(paths) 79 | end 80 | 81 | def formatter_regex 82 | @formatter_regex ||= /(?:^|\s)(?:-f\s*|--format(?:=|\s+))([\w:]+)/ 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/guard/jruby-rspec/templates/Guardfile: -------------------------------------------------------------------------------- 1 | # This eliminates the stty warning. 2 | interactor :simple 3 | 4 | guard 'jruby-rspec', :spec_paths => ["spec"] do 5 | # You can leave this empty and jruby-rspec will not autorun, 6 | # but it will run all specs in :spec_paths on demand 7 | 8 | # or you can configure it just like guard-rspec 9 | watch(%r{^spec/.+_spec\.rb$}) 10 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 11 | watch('spec/spec_helper.rb') { "spec" } 12 | 13 | # Rails example 14 | watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 15 | watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } 16 | watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } 17 | watch(%r{^spec/support/(.+)\.rb$}) { "spec" } 18 | watch('config/routes.rb') { "spec/routing" } 19 | watch('app/controllers/application_controller.rb') { "spec/controllers" } 20 | # Capybara request specs 21 | watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" } 22 | end 23 | 24 | -------------------------------------------------------------------------------- /lib/guard/jruby-rspec/version.rb: -------------------------------------------------------------------------------- 1 | module Guard 2 | module JRubyRSpecVersion 3 | VERSION = "0.2.3.dev" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/guard/jruby-rspec/containment_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'guard/jruby-rspec/containment' 3 | 4 | class Guard::JRubyRSpec 5 | describe Containment do 6 | subject(:containment) { described_class.new } 7 | 8 | describe '#protect' do 9 | it 'runs the block that is passed to it' do 10 | expect { |block| containment.protect &block }.to yield_control 11 | end 12 | 13 | it 'uses a default error_handler' do 14 | Guard::UI.should_receive(:error).at_least(1).times 15 | expect { containment.protect { raise 'busted' } }.to throw_symbol(:task_has_failed) 16 | end 17 | 18 | context 'with a custom error_handler' do 19 | subject(:containment) { described_class.new(:error_handler => lambda { |error| @custom_handler_called = true }) } 20 | 21 | it 'calls the custom error_handler' do 22 | Guard::UI.should_receive(:error).never 23 | expect { containment.protect { raise 'busted' } }.to throw_symbol(:task_has_failed) 24 | @custom_handler_called.should be_true 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/guard/jruby-rspec/reloaders_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Reloaders do 4 | subject(:reloaders) { described_class.new } 5 | 6 | describe 'storing blocks to be executed at reload time' do 7 | it 'passes the paths to be reloaded to the reloaders' do 8 | reloaders.register do |paths| 9 | paths.should == ['/path/to/file'] 10 | end 11 | reloaders.reload ['/path/to/file'] 12 | end 13 | 14 | describe 'the order in which reloaders are executed' do 15 | before :each do 16 | @a = @b = @counter = 0 17 | reloaders.register do 18 | @counter += 1 19 | @a = @counter 20 | end 21 | reloaders.register(:prepend => prepend) do 22 | @counter += 1 23 | @b = @counter 24 | end 25 | reloaders.reload 26 | end 27 | 28 | context 'in normal order' do 29 | let(:prepend) { false } 30 | it 'reloads in the same order as reloaders registered' do 31 | @a.should == 1 32 | @b.should == 2 33 | end 34 | end 35 | 36 | context 'with the 2nd reloader prepended' do 37 | let(:prepend) { true } 38 | it 'reloads in the same order as reloaders registered' do 39 | @a.should == 2 40 | @b.should == 1 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/guard/jruby-rspec/runner_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class Guard::JRubyRSpec::Runner::UI; end 4 | 5 | describe Guard::JRubyRSpec::Runner do 6 | subject { described_class.new } 7 | 8 | describe '#run' do 9 | 10 | before(:each) do 11 | Guard::JRubyRSpec::Runner::UI.stub(:info) 12 | end 13 | 14 | it 'keeps the RSpec global configuration between runs' do 15 | RSpec::Core::Runner.stub(:run) 16 | orig_configuration = ::RSpec.configuration 17 | ::RSpec.should_receive(:instance_variable_set).with(:@configuration, orig_configuration) 18 | 19 | subject.run(['spec/foo']) 20 | end 21 | 22 | context 'when passed an empty paths list' do 23 | it 'returns false' do 24 | subject.run([]).should be_false 25 | end 26 | end 27 | 28 | context 'when one of the source files is bad' do 29 | it 'recovers from syntax errors in files by displaying the error' do 30 | RSpec::Core::Runner.stub(:run).and_raise(SyntaxError.new('Bad Karma')) 31 | Guard::UI.should_receive(:error).at_least(1).times 32 | expect { 33 | subject.run(['spec/foo']) 34 | }.to throw_symbol(:task_has_failed) 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/guard/jruby-rspec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Guard::JRubyRSpec do 4 | let(:default_options) do 5 | { 6 | :focus_on_failed => false, 7 | :all_after_pass => true, 8 | :all_on_start => true, 9 | :keep_failed => true, 10 | :spec_paths => ['spec'], 11 | :spec_file_suffix => "_spec.rb", 12 | :run_all => {}, 13 | :monitor_file => ".guard-jruby-rspec", 14 | :custom_reloaders => [] 15 | } 16 | end 17 | 18 | let(:custom_watchers) do 19 | [Guard::Watcher.new(%r{^spec/(.+)$}, lambda { |m| "spec/#{m[1]}_match"})] 20 | end 21 | 22 | subject { described_class.new custom_watchers, default_options} 23 | 24 | let(:inspector) { mock(described_class::Inspector, :excluded= => nil, :spec_paths= => nil, :spec_paths => [], :clean => []) } 25 | let(:runner) { mock(described_class::Runner, :set_rspec_version => nil, :rspec_version => nil) } 26 | 27 | before do 28 | described_class::Runner.stub(:new => runner) 29 | described_class::Inspector.stub(:new => inspector) 30 | Guard::UI.stub(:info) 31 | end 32 | 33 | shared_examples_for 'clear failed paths' do 34 | it 'should clear the previously failed paths' do 35 | inspector.stub(:clean).and_return(['spec/foo_match'], ['spec/bar_match']) 36 | 37 | runner.should_receive(:run).with(['spec/foo_match']) { false } 38 | expect { subject.run_on_change(['spec/foo']) }.to throw_symbol :task_has_failed 39 | 40 | runner.should_receive(:run) { true } 41 | expect { subject.run_all }.to_not throw_symbol # this actually clears the failed paths 42 | 43 | runner.should_receive(:run).with(['spec/bar_match']) { true } 44 | subject.run_on_change(['spec/bar']) 45 | end 46 | end 47 | 48 | describe '.initialize' do 49 | it 'creates an inspector' do 50 | described_class::Inspector.should_receive(:new).with(default_options.merge(:foo => :bar)) 51 | 52 | described_class.new([], :foo => :bar) 53 | end 54 | 55 | it 'creates a runner' do 56 | described_class::Runner.should_receive(:new).with(default_options.merge(:foo => :bar)) 57 | 58 | described_class.new([], :foo => :bar) 59 | end 60 | end 61 | 62 | describe '#start' do 63 | it 'calls #run_all' do 64 | subject.should_receive(:run_all) 65 | subject.start 66 | end 67 | 68 | context ':all_on_start option is false' do 69 | let(:subject) { subject = described_class.new([], :all_on_start => false) } 70 | 71 | it "doesn't call #run_all" do 72 | subject.should_not_receive(:run_all) 73 | subject.start 74 | end 75 | end 76 | end 77 | 78 | describe '#run_all' do 79 | before { inspector.stub(:spec_paths => ['spec']) } 80 | 81 | it "runs all specs specified by the default 'spec_paths' option" do 82 | inspector.stub(:spec_paths => ['spec', 'spec/fixtures/other_spec_path']) 83 | runner.should_receive(:run).with(['spec', 'spec/fixtures/other_spec_path'], anything) { true } 84 | 85 | subject.run_all 86 | end 87 | 88 | it 'passes the :run_all options' do 89 | subject = described_class.new([], { 90 | :rvm => ['1.8.7', '1.9.2'], :cli => '--color', :run_all => { :cli => '--format progress' } 91 | }) 92 | runner.should_receive(:run).with(['spec'], hash_including(:cli => '--format progress')) { true } 93 | 94 | subject.run_all 95 | end 96 | 97 | it 'passes the message to the runner' do 98 | runner.should_receive(:run).with(['spec'], hash_including(:message => 'Running all specs')) { true } 99 | 100 | subject.run_all 101 | end 102 | 103 | it "throws task_has_failed if specs don't passed" do 104 | runner.should_receive(:run) { false } 105 | 106 | expect { subject.run_all }.to throw_symbol :task_has_failed 107 | end 108 | 109 | it_should_behave_like 'clear failed paths' 110 | end 111 | 112 | describe '#reload_rails' do 113 | it 'continues silently if the supported Rails 3.2+ version of Rails reloading is not supported' do 114 | defined?(::ActionDispatch::Reloader).should be_false 115 | expect { 116 | subject.reload_rails 117 | }.not_to raise_exception 118 | end 119 | 120 | it "reloads Rails if it's loaded" do 121 | stub_const '::ActionDispatch::Reloader', double 122 | ActionDispatch::Reloader.should_receive 'cleanup!' 123 | ActionDispatch::Reloader.should_receive 'prepare!' 124 | subject.reload_rails 125 | end 126 | end 127 | 128 | describe '#reload_factory_girl' do 129 | it 'continues silently if FactoryGirl is not loaded' do 130 | defined?(::FactoryGirl).should be_false 131 | expect { 132 | subject.reload_factory_girl 133 | }.not_to raise_exception 134 | end 135 | 136 | it "reloads FactoryGirl if it's loaded" do 137 | stub_const 'FactoryGirl', double 138 | FactoryGirl.should_receive 'reload' 139 | subject.reload_factory_girl 140 | end 141 | end 142 | 143 | describe '#reload_paths' do 144 | it 'should reload files other than spec files' do 145 | lib_file = 'lib/myapp/greeter.rb' 146 | spec_file = 'specs/myapp/greeter_spec.rb' 147 | File.stub(:exists?).and_return(true) 148 | subject.stub(:load) 149 | subject.should_receive(:load).with(lib_file) 150 | subject.should_not_receive(:load).with(spec_file) 151 | 152 | subject.reload_paths([lib_file, spec_file]) 153 | end 154 | 155 | it 'should use @options to alter spec file suffix' do 156 | subject = described_class.new([], :spec_file_suffix => '_test.rb') 157 | test_file = 'specs/myapp/greeter_test.rb' 158 | File.stub(:exists?).and_return(true) 159 | subject.stub(:load) 160 | subject.should_not_receive(:load).with(test_file) 161 | 162 | subject.reload_paths([test_file]) 163 | end 164 | 165 | it 'recovers from exceptions raised when loading files' do 166 | lib_file = 'lib/myapp/greeter.rb' 167 | File.stub(:exists?).and_return(true) 168 | subject.stub(:load).and_raise("This fires and deactivates the jruby-rspec guard") 169 | Guard::UI.should_receive(:error).any_number_of_times 170 | expect { 171 | subject.reload_paths([lib_file]) 172 | }.to throw_symbol(:task_has_failed) 173 | end 174 | end 175 | 176 | describe '#run_on_change' do 177 | before { inspector.stub(:clean => ['spec/foo_match']) } 178 | 179 | it 'runs rspec with paths' do 180 | runner.should_receive(:run).with(['spec/foo_match']) { true } 181 | 182 | subject.run_on_change(['spec/foo']) 183 | end 184 | 185 | context 'the changed specs pass after failing' do 186 | it 'calls #run_all' do 187 | runner.should_receive(:run).with(['spec/foo_match']) { false } 188 | 189 | expect { subject.run_on_change(['spec/foo']) }.to throw_symbol :task_has_failed 190 | 191 | runner.should_receive(:run).with(['spec/foo_match']) { true } 192 | subject.should_receive(:run_all) 193 | 194 | expect { subject.run_on_change(['spec/foo']) }.to_not throw_symbol 195 | end 196 | 197 | context ':all_after_pass option is false' do 198 | subject { described_class.new(custom_watchers, :all_after_pass => false) } 199 | 200 | it "doesn't call #run_all" do 201 | runner.should_receive(:run).with(['spec/foo_match']) { false } 202 | 203 | expect { subject.run_on_change(['spec/foo']) }.to throw_symbol :task_has_failed 204 | 205 | runner.should_receive(:run).with(['spec/foo_match']) { true } 206 | subject.should_not_receive(:run_all) 207 | 208 | expect { subject.run_on_change(['spec/foo']) }.to_not throw_symbol 209 | end 210 | end 211 | end 212 | 213 | context 'the changed specs pass without failing' do 214 | it "doesn't call #run_all" do 215 | runner.should_receive(:run).with(['spec/foo_match']) { true } 216 | 217 | subject.should_not_receive(:run_all) 218 | 219 | subject.run_on_change(['spec/foo']) 220 | end 221 | end 222 | 223 | it 'keeps failed spec and rerun them later' do 224 | subject = described_class.new(custom_watchers, :all_after_pass => false) 225 | 226 | inspector.should_receive(:clean).with(['spec/bar_match']).and_return(['spec/bar_match']) 227 | runner.should_receive(:run).with(['spec/bar_match']) { false } 228 | 229 | expect { subject.run_on_change(['spec/bar']) }.to throw_symbol :task_has_failed 230 | 231 | inspector.should_receive(:clean).with(['spec/foo_match', 'spec/bar_match']).and_return(['spec/foo_match', 'spec/bar_match']) 232 | runner.should_receive(:run).with(['spec/foo_match', 'spec/bar_match']) { true } 233 | 234 | subject.run_on_change(['spec/foo']) 235 | 236 | inspector.should_receive(:clean).with(['spec/foo_match']).and_return(['spec/foo_match']) 237 | runner.should_receive(:run).with(['spec/foo_match']) { true } 238 | 239 | subject.run_on_change(['spec/foo']) 240 | end 241 | 242 | it "throws task_has_failed if specs doesn't pass" do 243 | runner.should_receive(:run).with(['spec/foo_match']) { false } 244 | 245 | expect { subject.run_on_change(['spec/foo']) }.to throw_symbol :task_has_failed 246 | end 247 | 248 | it "works with watchers that have an array of test targets" do 249 | subject = described_class.new([Guard::Watcher.new(%r{^spec/(.+)$}, lambda { |m| ["spec/#{m[1]}_match", "spec/#{m[1]}_another.rb"]})]) 250 | 251 | test_targets = ["spec/quack_spec_match", "spec/quack_spec_another.rb"] 252 | 253 | inspector.should_receive(:clean).with(test_targets).and_return(test_targets) 254 | runner.should_receive(:run).with(test_targets) { true } 255 | subject.run_on_change(['spec/quack_spec']) 256 | 257 | end 258 | 259 | 260 | it "works with watchers that don't have an action" do 261 | subject = described_class.new([Guard::Watcher.new(%r{^spec/(.+)$})]) 262 | 263 | inspector.should_receive(:clean).with(anything).and_return(['spec/quack_spec']) 264 | runner.should_receive(:run).with(['spec/quack_spec']) { true } 265 | 266 | subject.run_on_change(['spec/quack_spec']) 267 | end 268 | 269 | it "works with watchers that do have an action" do 270 | watcher_with_action = mock(Guard::Watcher, :match => :matches, :action => true) 271 | watcher_with_action.should_receive(:call_action).with(:matches).and_return('spec/foo_match') 272 | 273 | subject = described_class.new([watcher_with_action]) 274 | 275 | inspector.should_receive(:clean).with(['spec/foo_match']).and_return(['spec/foo_match']) 276 | runner.should_receive(:run).with(['spec/foo_match']) { true } 277 | 278 | subject.run_on_change(['spec/foo']) 279 | end 280 | end 281 | end 282 | 283 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'guard/jruby-rspec' 3 | ENV["GUARD_ENV"] = 'test' 4 | 5 | RSpec.configure do |config| 6 | config.color_enabled = true 7 | config.filter_run :focus => true 8 | config.run_all_when_everything_filtered = true 9 | config.treat_symbols_as_metadata_keys_with_true_values = true 10 | 11 | config.before(:each) do 12 | @fixture_path = Pathname.new(File.expand_path('../fixtures/', __FILE__)) 13 | @lib_path = Pathname.new(File.expand_path('../../lib/', __FILE__)) 14 | end 15 | 16 | end 17 | --------------------------------------------------------------------------------